commit 4588950b313a7e846e4b3d68dfebc767b888b5b5 Author: Farnam Jahangard Date: Sun Apr 12 20:44:08 2026 -0700 added files diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml new file mode 100644 index 0000000..7ab6188 --- /dev/null +++ b/.github/workflows/cmake-single-platform.yml @@ -0,0 +1,25 @@ +name: CMake Build + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build --config Release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99a3d93 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Linker files +*.ilk + +# Debugger Files +*.pdb + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# debug information files +*.dwo + +includes/temp.cpp \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..de44cbc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.10) +project(breakoutpp) + +file(GLOB SOURCES + main.cpp + includes/*.cpp +) + +add_executable(breakoutpp ${SOURCES}) +target_include_directories(breakoutpp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/includes) diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd64629 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Breakout++ + +![Game Screenshot](src/screenshots/macOS/game.png) + +An implementation of the game "Athari breakout" in cpp + +this is me and my friend [Matin](https://github.com/Matin-Ardestani) freshman year first semester project. + +### How to play + +there is a help menu that describes how you should play the game and how it works but basically : + +you can move your paddle with "a" and "d" to catch the ball +you can press "p" to pause the game. + +### Build Instruction + +to build using cmake : + +```shell +git clone https://github.com/farnam-jhn/breakoutpp.git +cd breakout +mkdir build +cd build +mkdir src +touch src/history.txt +cmake .. +cmake --build . +./breakoutpp +``` +or you can use normal gcc compiler : + +```shell +git clone https://github.com/farnam-jhn/breakoutpp.git +cd breakout +g++ -o breakoutpp main.cpp includes/*.cpp +./breakoutpp +``` + +### Note + +currently only supports unix-like operating systems (e.g Linux, macOS, BSD) +you can compile and play the game in windows using [WSL](https://learn.microsoft.com/en-us/windows/wsl/) diff --git a/colorsguide.txt b/colorsguide.txt new file mode 100644 index 0000000..970c5fe --- /dev/null +++ b/colorsguide.txt @@ -0,0 +1,25 @@ +you should cout this in order to get colored output on terminal: + + + +\033[m\033[0m + + +color codes : + 30-37: Foreground colors (30=black, 31=red, 32=green, 33=yellow, 34=blue, 35=magenta, 36=cyan, 37=white) + + 40-47: Background colors (same color order as foreground) + + 0: Reset to default + + 1: Bold/bright + + 4: Underline + +in order to use color + bold/underline you should use ; coloumn + +e.g. + +std::cout << "\033[31mThis is red text\033[0m" << std::endl; +std::cout << "\033[32mThis is green text\033[0m" << std::endl; +std::cout << "\033[1;34mThis is bold blue text\033[0m" << std::endl; diff --git a/cppfilestemplate.txt b/cppfilestemplate.txt new file mode 100644 index 0000000..3dbf3b7 --- /dev/null +++ b/cppfilestemplate.txt @@ -0,0 +1,21 @@ +// Headers + + + +// Structures + + + +// Global variables + + + +// Functions prototype + + + +// Main function + + + +// Functions diff --git a/demo.mp4 b/demo.mp4 new file mode 100644 index 0000000..907b24c Binary files /dev/null and b/demo.mp4 differ diff --git a/includes/board.cpp b/includes/board.cpp new file mode 100644 index 0000000..6a4a071 --- /dev/null +++ b/includes/board.cpp @@ -0,0 +1,244 @@ +// Headers + +#include +#include +#include +#include +#include +#include +#include +#include +#include "getchar.h" +#include "setcursor.h" +#include "structs.h" +using namespace std; + +// Global varialbes + +extern string board[30][80]; +extern string hud[30][20]; +extern int board_width; +extern int board_lenght; +extern int bricks_idx[36], bricks_idy[36]; +extern int hudLength; +extern int hudWidth; +extern int bricksCount; +extern std::atomic running; +extern std::atomic paused; + +extern string ballChar ; +extern string block ; +extern string blockRed ; +extern string blockGreen; +extern string blockBlue ; +extern string blockYellow ; +extern string trCorner ; +extern string tlCorner ; +extern string brCorner ; +extern string blCorner ; +extern string paddeleLine ; +extern string horizontalLine ; +extern string verticalLine ; +extern Player player; + +// Function Prototypes + +void hudCalculation(); +string bCountInString(); +string scoreInString(); +bool isBrick(int, int); + +// Functions + +void drawBoard(){ + + hudCalculation(); + gotoxy(0, 0); + string currentChar; + cout << endl << endl; + for(int i = 0; i < board_width; i++){ + cout << " "; + for(int j = 0; j < board_lenght + hudLength ; j++){ + if (j < hudLength){ + cout << hud[i][j]; + } + else { + int boardCol = j - hudLength; + currentChar = board[i][boardCol]; + if (currentChar == horizontalLine || + currentChar == verticalLine || + currentChar == tlCorner || + currentChar == trCorner || + currentChar == blCorner || + currentChar == brCorner || + currentChar == ballChar){ + cout << currentChar; + } + else if (isBrick(i, boardCol)) { + + int nameSeed1 = (int)player.name[0]; + int nameSeed2 = (int)player.name[1]; + + std::mt19937 gen(i * nameSeed1 + j * nameSeed2); // generates a random based on seed (a combination of i and j with player's name) + + std::vector colors = {blockRed, blockBlue, blockGreen, blockYellow, block}; // Creates a dynamic array containing the colored blocks characters + std::shuffle(colors.begin(), colors.end(), gen); // shuffles the colors dynamic array + + for(int k = 0; k < 5; k++) { + std::cout << colors[0]; + } + + j += 4; + } + else if (currentChar == paddeleLine){ + for (int k = 0 ; k < 10 ; k++){ + cout << paddeleLine; + } + j += 9; + } + else { + cout << " "; + } + } + } + cout << endl; + + } +} + +// checking if a certain location contains a brick or not + +bool isBrick(int x, int y){ + for(int i = 0, j = 0; i < 36; i++, j++){ + if(bricks_idx[i] == x){ + if(bricks_idy[i] == y){ + return true; + } + } + } + return false; +} + +// Head up display + +void hudCalculation(){ + + // setting up borders + for(int i = 1; i < hudLength - 1; i++){ + hud[0][i] = horizontalLine; // top + hud[hudWidth - 1][i] = horizontalLine; // bottom + } + for(int i = 1; i < hudWidth - 1; i++){ + hud[i][0] = verticalLine; // right + hud[i][hudLength - 1] = verticalLine; // left + } + // setting up corners + + hud[0][0] = tlCorner; + hud[0][hudLength - 1] = trCorner; + hud[hudWidth - 1][0] = blCorner; + hud[hudWidth - 1][hudLength - 1] = brCorner; + + for (int i = 1; i < hudWidth - 1; i++){ + for (int j = 1; j < hudLength - 1; j++){ + hud[i][j] = " "; + } + } + + + string score = " Score : " + scoreInString(); + for (int i = 0; i < 15; i++){ + hud[2][i + 2] = score[i]; + } + + string health = " Health : " + to_string(player.health); + + for (int i = 0 ; i < 11 ; i++){ + hud[4][i + 2] = health[i]; + } + + string bricksLine = " Bricks : " + bCountInString(); + + for (int i = 0 ; i < 12 ; i++){ + hud[6][i + 2] = bricksLine[i]; + } + + if (paused) { + string pauseText = " PAUSED "; + for (int i = 0; i < 8; i++){ + hud[8][i + 2] = pauseText[i]; + } + } +} + +// Proccessing input + +int inputProccessing(Paddle &paddle){ + + using namespace std::chrono_literals; // for sleep function + + char inputChar = getch(); + bool moved = false; + int temp = paddle.start_loc.x; + switch (inputChar) { + case 'p': + case 'P': + paused = !paused; + return 1; + break; + case 'a' : + case 'A' : + if (!paused && paddle.start_loc.x > 1) { + paddle.start_loc.x -= 3; + + if (paddle.start_loc.x < 1) { + paddle.start_loc.x = 1; + } + moved = true; + } + break; + case 'd' : + case 'D' : + if (!paused && paddle.start_loc.x < board_lenght - 11) { + + paddle.start_loc.x += 3; + + if (paddle.start_loc.x > board_lenght - 11) { + paddle.start_loc.x = board_lenght - 11; + } + moved = true; + } + break; + case 'q': + return 0; + break; + case 'Q': + return 0; + break; + + default: + return 1; + } + + // Changing paddle location + + if (!paused && moved){ + board[board_width - 2][paddle.start_loc.x] = paddeleLine; + board[board_width - 2][temp] = " "; + } + + + return 1; +} + +string scoreInString(){ + string result = to_string(player.score); + result.insert(0, 6 - result.length(), '0'); + return result; +} + +string bCountInString(){ + string result = to_string(bricksCount); + result.insert(0, 2 - result.length(), '0'); + return result; +} diff --git a/includes/board.h b/includes/board.h new file mode 100644 index 0000000..7048579 --- /dev/null +++ b/includes/board.h @@ -0,0 +1,12 @@ +#pragma once +#ifndef BOARD_H +#define BOARD_H +#include "getchar.h" +#include "structs.h" +#include + +void drawBoard(); +bool isBrick(int, int); +int inputProccessing(Paddle &paddle); + +#endif diff --git a/includes/cursorhide.cpp b/includes/cursorhide.cpp new file mode 100644 index 0000000..3c7b6d6 --- /dev/null +++ b/includes/cursorhide.cpp @@ -0,0 +1,16 @@ +#include +#ifdef _WIN32 +#include +#endif + +void show_console_cursor(bool show) { +#if defined(_WIN32) + HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_CURSOR_INFO cci; + GetConsoleCursorInfo(handle, &cci); + cci.bVisible = show; + SetConsoleCursorInfo(handle, &cci); +#else + std::cout << (show ? "\033[?25h" : "\033[?25l"); +#endif +} diff --git a/includes/cursorhide.h b/includes/cursorhide.h new file mode 100644 index 0000000..a00e230 --- /dev/null +++ b/includes/cursorhide.h @@ -0,0 +1,7 @@ +#pragma once +#include +#ifdef _WIN32 +#include +#endif + +void show_console_cursor(bool show); diff --git a/includes/getchar.h b/includes/getchar.h new file mode 100644 index 0000000..8338338 --- /dev/null +++ b/includes/getchar.h @@ -0,0 +1,25 @@ +#pragma once // prevents multiple inclusion +#include + +#if defined(_WIN32) || defined(_WIN64) +#include // Windows +inline char mygetch() { + return _getch(); // Windows getch +} +#else +#include +#include +inline char getch() { + char buf = 0; + struct termios old = {0}; + tcgetattr(0, &old); + old.c_lflag &= ~ICANON; // disable line buffering + old.c_lflag &= ~ECHO; // disable echo + tcsetattr(0, TCSANOW, &old); + read(0, &buf, 1); + old.c_lflag |= ICANON; + old.c_lflag |= ECHO; + tcsetattr(0, TCSANOW, &old); + return buf; +} +#endif diff --git a/includes/history.cpp b/includes/history.cpp new file mode 100644 index 0000000..17e56f9 --- /dev/null +++ b/includes/history.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include "getchar.h" +using namespace std; + +extern string trCorner ; +extern string tlCorner ; +extern string brCorner ; +extern string blCorner ; +extern string paddeleLine ; +extern string horizontalLine ; +extern string verticalLine ; + +/* + history format: + PlayerName, Best socre, Best socre time, Last score, Last score time +*/ + +void saveData(string name, int score){ + + ifstream history_reader("src/history.txt"); + if(!history_reader.is_open()){ + cout << "ERROR OPENNING HISTORY!"; + return; + } + + time_t now = time(nullptr); + string date = ctime(&now); + date.pop_back(); // delete \n at the end + + string output = ""; + bool new_player = true; + + string line; + + while(getline(history_reader, line)){ + + int splitter[4]; + splitter[0] = line.find(","); + splitter[1] = line.find(",", splitter[0] + 1); + splitter[2] = line.find(",", splitter[1] + 1); + splitter[3] = line.find(",", splitter[2] + 1); + + string player_name = line.substr(0, splitter[0]); + int best_score = stoi(line.substr(splitter[0] + 1, splitter[1] - splitter[0] - 1)); + string best_score_date = line.substr(splitter[1] + 1, splitter[2] - splitter[1] - 1); + int last_score = stoi(line.substr(splitter[2] + 1, splitter[3] - splitter[2] - 1)); + string last_score_date = line.substr(splitter[3] + 1); + + if(player_name == name){ + new_player = false; + + if(score >= best_score){ + best_score = score; + best_score_date = date; + } + + last_score = score; + last_score_date = date; + } + + output += player_name + "," + + to_string(best_score) + "," + + best_score_date + "," + + to_string(last_score) + "," + + last_score_date + "\n"; + } + + history_reader.close(); + + if(new_player){ + output += name + "," + + to_string(score) + "," + + date + "," + + to_string(score) + "," + + date + "\n"; + } + + ofstream history_write("src/history.txt", ios::trunc); + history_write << output; + history_write.close(); +} + +void showHistory(){ + ifstream history("src/history.txt"); + if(!history.is_open()){ + cout << "ERROR OPENNING HISTORY!"; + return; + } + + system("clear"); + const int tableWidth = 100; + // Top border + cout << endl << endl; + cout << tlCorner; + for(int i = 0; i < tableWidth; i++) cout << horizontalLine; + cout << trCorner << endl; + // Title + cout << verticalLine + << "\033[33m" // yellow + << setw(tableWidth) << left << " GAME HISTORY" + << "\033[0m" // reset + << verticalLine << endl; + // Separator + cout << verticalLine; + for(int i = 0; i < tableWidth; i++) cout << horizontalLine; + cout << verticalLine << endl; + // Header + cout << verticalLine << " " + << "\033[36m" // cyan + << setw(15) << left << "Player" + << setw(12) << "Best Score" + << setw(25) << "Best Date" + << setw(14) << "Last Score" + << setw(32) << "Last Date" + << "\033[0m" // reset + << " " << verticalLine << endl; + // Header separator + cout << verticalLine; + for(int i = 0; i < tableWidth; i++) cout << horizontalLine; + cout << verticalLine << endl; + string line; + while(getline(history, line)){ + + int splitter[4]; + splitter[0] = line.find(","); + splitter[1] = line.find(",", splitter[0] + 1); + splitter[2] = line.find(",", splitter[1] + 1); + splitter[3] = line.find(",", splitter[2] + 1); + + string player_name = line.substr(0, splitter[0]); + string best_score = line.substr(splitter[0] + 1, splitter[1] - splitter[0] - 1); + string best_score_date = line.substr(splitter[1] + 1, splitter[2] - splitter[1] - 1); + string last_score = line.substr(splitter[2] + 1, splitter[3] - splitter[2] - 1); + string last_score_date = line.substr(splitter[3] + 1); + + cout << verticalLine << " " + << "\033[32m" << setw(15) << left << player_name << "\033[0m" // green - reset + << "\033[35m" << setw(12) << best_score << "\033[0m" // purple - reset + << setw(16) << best_score_date << " " + << "\033[35m" << setw(14) << last_score << "\033[0m" // purple - reset + << setw(32) << last_score_date + << " " << verticalLine << endl; + } + // Bottom border + cout << blCorner; + for(int i = 0; i < tableWidth; i++) cout << horizontalLine; + cout << brCorner << endl; + + // escape + getch(); +} diff --git a/includes/history.h b/includes/history.h new file mode 100644 index 0000000..d93a1c1 --- /dev/null +++ b/includes/history.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include +#include +#include +#include "getchar.h" +using namespace std; + +void saveData(string name, int score); +void showHistory(); diff --git a/includes/mechanic.cpp b/includes/mechanic.cpp new file mode 100644 index 0000000..44f9aef --- /dev/null +++ b/includes/mechanic.cpp @@ -0,0 +1,216 @@ +#include "structs.h" +#include "board.h" +#include +#include +#include + +using namespace std; + +extern Ball ball; +extern Player player; +extern int board_lenght; +extern int board_width; +extern string board[30][80]; +extern string ballChar; +extern int bricks_idx[36]; +extern int bricks_idy[36]; +extern string paddeleLine ; +extern Paddle paddle; +extern int bricksCount; +extern std::atomic paused; +extern Brick bricks[36]; + +void ballmover(Ball &ball){ + + if (player.health == 0){ + player.gameover = true; + } + else if (bricksCount == 0){ + player.won = true; + } + + using namespace std::chrono_literals; + + // Calculate new position + int tempNewX = ball.loc.x + ball.v.vX; + int tempNewY = ball.loc.y + ball.v.vY; + + // Checking collision for current position + for(int i = 0; i < 36; i++){ + if(bricks_idx[i] != -1 && bricks_idy[i] != -1){ + int brick_row = bricks_idx[i]; + int brick_col_start = bricks_idy[i]; + + // Check if ball is stuck in a brick + if(ball.loc.y == brick_row && + ball.loc.x >= brick_col_start && + ball.loc.x <= brick_col_start + 4){ + // reverse both velocities + ball.v.vX = -ball.v.vX; + ball.v.vY = -ball.v.vY; + tempNewX = ball.loc.x + ball.v.vX; + tempNewY = ball.loc.y + ball.v.vY; + // delete brick and add score + player.score += bricks[i].score; + bricksCount--; + bricks_idx[i] = -1; + bricks_idy[i] = -1; + + goto skip_normal_collision; // Skip rest of collision checks + } + } + } + + // Checking brick collison for new position + for(int i = 0; i < 36; i++){ + if(bricks_idx[i] != -1 && bricks_idy[i] != -1){ + int brick_row = bricks_idx[i]; + int brick_col_start = bricks_idy[i]; + int brick_col_end = brick_col_start + 4; + + // checking if there is a brick there, Works like (isBrick( , )) function + if(tempNewY == brick_row && + tempNewX >= brick_col_start && + tempNewX <= brick_col_end){ + + bool bounceX = false; + bool bounceY = false; + + // X direction + if(ball.v.vX > 0 && ball.loc.x < brick_col_start){ // heading right + bounceX = true; + } + else if(ball.v.vX < 0 && ball.loc.x > brick_col_end){ // heading left + bounceX = true; + } + + // Y direction + if(ball.v.vY > 0 && ball.loc.y < brick_row){ // heading down + bounceY = true; + } + else if(ball.v.vY < 0 && ball.loc.y > brick_row){ // heading up + bounceY = true; + } + + if(bounceX && bounceY){ // Corner hit + + // fixing the special case problem. in this case two bricks collapse at once + bool special_case_handled = false; // flag + if(i < 23 && i % 12 != 0 && ball.v.vX > 0){ + if(bricks_idx[i - 1] != -1 && bricks_idx[i + 12] != -1){ + // delete brick and add score ( two bricks ) + player.score += (bricks[i - 1].score + bricks[i + 12].score); + bricksCount -= 2; + bricks_idx[i - 1] = -1; + bricks_idy[i - 1] = -1; + bricks_idx[i + 12] = -1; + bricks_idy[i + 12] = -1; + special_case_handled = true; + } + } + else if(i < 23 && i % 12 != 11 && ball.v.vX < 0){ + if(bricks_idx[i + 1] != -1 && bricks_idx[i + 12] != -1){ + // delete brick and add score ( two bricks ) + player.score += (bricks[i + 1].score + bricks[i + 12].score); + bricksCount -= 2; + bricks_idx[i + 1] = -1; + bricks_idy[i + 1] = -1; + bricks_idx[i + 12] = -1; + bricks_idy[i + 12] = -1; + special_case_handled = true; + } + } + + if(!special_case_handled){ // no special case accured + // delete brick and add score + player.score += bricks[i].score; + bricksCount--; + bricks_idx[i] = -1; + bricks_idy[i] = -1; + } + + + ball.v.vX = -ball.v.vX; + ball.v.vY = -ball.v.vY; + } + else if(bounceX){ // Side hit + ball.v.vX = -ball.v.vX; + // delete brick and add score + player.score += bricks[i].score; + bricksCount--; + bricks_idx[i] = -1; + bricks_idy[i] = -1; + } + else { // Top/bottom hit + ball.v.vY = -ball.v.vY; + // delete brick and add score + player.score += bricks[i].score; + bricksCount--; + bricks_idx[i] = -1; + bricks_idy[i] = -1; + } + + // Recalculate position after bounce + tempNewX = ball.loc.x + ball.v.vX; + tempNewY = ball.loc.y + ball.v.vY; + + break; // Only handle one brick per frame + } + } + } + + skip_normal_collision: + + // Wall collision + if (tempNewX <= 0 || tempNewX >= board_lenght - 1){ + ball.v.vX = -ball.v.vX; + tempNewX = ball.loc.x + ball.v.vX; + } + + if (tempNewY == board_width - 1){ + player.health--; + ball.v.vY = -ball.v.vY; + tempNewY = ball.loc.y + ball.v.vY; + } + else if (tempNewY <= 0 || tempNewY >= board_width - 1){ + ball.v.vY = -ball.v.vY; + tempNewY = ball.loc.y + ball.v.vY; + } + + + + if (tempNewY == board_width - 2) { + // Check if the ball's X position is inside the paddle (width 10) + if (tempNewX >= paddle.start_loc.x && tempNewX < paddle.start_loc.x + 10) { + + int distanceFromPS = tempNewX - paddle.start_loc.x; + + ball.v.vY = -1; // Force ball to move UP + + int velocityMagnitude[10] = {3,2,2,1,1,1,1,2,2,3}; // an array that gives any x axis distance between the ball and paddle start a velocity + + if (ball.v.vX > 0){ + ball.v.vX = velocityMagnitude[distanceFromPS]; + } + else { + ball.v.vX = -velocityMagnitude[distanceFromPS]; + } + + tempNewY = ball.loc.y + ball.v.vY; // Update next Y immediately + + } + } + // Clear old position + + if (ball.loc.x != tempNewX || ball.loc.y != tempNewY) { + board[ball.loc.y][ball.loc.x] = " "; + } + + // Update position + ball.loc.x = tempNewX; + ball.loc.y = tempNewY; + + + // Draw ball at new position + board[ball.loc.y][ball.loc.x] = ballChar; +} diff --git a/includes/mechanic.h b/includes/mechanic.h new file mode 100644 index 0000000..049d64d --- /dev/null +++ b/includes/mechanic.h @@ -0,0 +1,3 @@ +#include "structs.h" + +void ballmover(Ball &ball); diff --git a/includes/menu.cpp b/includes/menu.cpp new file mode 100644 index 0000000..5adac33 --- /dev/null +++ b/includes/menu.cpp @@ -0,0 +1,96 @@ +#include +#include "getchar.h" + + + +char optionChoosenByUser(){ + + + std::cout << "\n\n\033[33m <-------Select option!------->\033[0m" << std::endl + << "\033[34m ╔══════════════╗ \033[0m" << std::endl + << "\033[34m ║\033[0m" + <<" 1. New Game " + <<"\033[34m║ \033[0m" << std::endl + << "\033[34m ╚══════════════╝ \033[0m" << std::endl << std::endl + << "\033[34m ╔══════════════╗ \033[0m" << std::endl + << "\033[34m ║\033[0m" + <<" 2. Help " + <<"\033[34m║ \033[0m" << std::endl + << "\033[34m ╚══════════════╝ \033[0m" << std::endl << std::endl + << "\033[34m ╔══════════════╗ \033[0m" << std::endl + << "\033[34m ║\033[0m" + <<" 3. History " + <<"\033[34m║ \033[0m" << std::endl + << "\033[34m ╚══════════════╝ \033[0m" << std::endl << std::endl + << "\033[34m ╔══════════════╗ \033[0m" << std::endl + << "\033[34m ║\033[0m" + <<" 4. Exit " + <<"\033[34m║ \033[0m" << std::endl + << "\033[34m ╚══════════════╝ \033[0m" << std::endl; + + char chosenOpt = getch(); + return chosenOpt; +} + +void helpMenu(){ + system("clear"); + std::cout << std::endl << std::endl + << "\033[34m ╔══════════════════════════════════════════════════════════════════════╗ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " The game consist of few elements : " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " 1. The ball, 2. Paddel, 3. Blocks " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " The ball starts moving in a line; whenever the ball hits the wall, " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " a block or the paddel it reflects like a beam and whenever it hits " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " a block, the block gets destroyed. " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " The goal is to destroy all of the blocks " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " If your ball fall down and don't touch the paddel you lose a " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " heart; you have 3 hearts, if you lose them all you lose. " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " You can move the paddel using \"A\" & \"D\" to move left & right. " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " Press \"P\" or \"p\" to pause the game. " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " Press any button to exit. " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ║\033[0m" + << " " + << "\033[34m║ \033[0m" << std::endl + << "\033[34m ╚══════════════════════════════════════════════════════════════════════╝ \033[0m" << std::endl; + + getch(); +} diff --git a/includes/menu.h b/includes/menu.h new file mode 100644 index 0000000..ac4f7de --- /dev/null +++ b/includes/menu.h @@ -0,0 +1,5 @@ +#include "getchar.h" +#include + +char optionChoosenByUser(); +void helpMenu(); diff --git a/includes/setcursor.cpp b/includes/setcursor.cpp new file mode 100644 index 0000000..273c45e --- /dev/null +++ b/includes/setcursor.cpp @@ -0,0 +1,14 @@ +#include +#ifdef _WIN32 +#include +#endif + +void gotoxy(int x, int y) { +#ifdef _WIN32 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + COORD pos = {(SHORT)x, (SHORT)y}; + SetConsoleCursorPosition(hConsole, pos); +#else + printf("\033[%d;%dH", y, x); +#endif +} diff --git a/includes/setcursor.h b/includes/setcursor.h new file mode 100644 index 0000000..75e7192 --- /dev/null +++ b/includes/setcursor.h @@ -0,0 +1,7 @@ +#pragma once +#include +#ifdef _WIN32 +#include +#endif + +void gotoxy(int x, int y); diff --git a/includes/structs.h b/includes/structs.h new file mode 100644 index 0000000..4f3706c --- /dev/null +++ b/includes/structs.h @@ -0,0 +1,36 @@ +#pragma once +#include + +struct Player{ + int score = 0; + std::string name; + bool initialInput = false; + int health = 3; + bool gameover = false; + bool won = false; +}; + +struct Location{ + int x; + int y; +}; + +struct Brick{ + Location loc; + int score; +}; + +struct Paddle{ + Location start_loc; + // int lenght; // possible feature, right now considered 10 +}; + +struct Velocity{ + int vX; + int vY; +}; + +struct Ball{ + Location loc; + Velocity v; +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..d5eaeb3 --- /dev/null +++ b/main.cpp @@ -0,0 +1,328 @@ +// Headers + +#include "includes/menu.h" +#include "includes/getchar.h" +#include "includes/board.h" +#include "includes/cursorhide.h" +#include "includes/structs.h" +#include "includes/mechanic.h" +#include "includes/history.h" +#include +#include +#include +#include +#include + +using namespace std::chrono; + + +// ----- Global variables ----- + +// Game components +int board_lenght = 80; +int board_width = 30; +std::string board[30][80]; +std::string hud[30][20]; +int hudLength = 20; +int hudWidth = 30; +int velocitySpeed[3] = {60,70,80}; +int bricksCount = 36; +Paddle paddle; +Ball ball; +Player player; + +Brick bricks[36]; +int bricks_idx[36], bricks_idy[36]; + +// Characters + +std::string ballChar = "\033[32m●\033[0m"; +std::string block = "█"; +std::string blockRed = "\033[31m█\033[0m"; +std::string blockGreen= "\033[32m█\033[0m"; +std::string blockBlue = "\033[34m█\033[0m"; +std::string blockYellow = "\033[33m█\033[0m"; +std::string trCorner = "\033[34m╗\033[0m"; +std::string tlCorner = "\033[34m╔\033[0m"; +std::string brCorner = "\033[34m╝\033[0m"; +std::string blCorner = "\033[34m╚\033[0m"; +std::string paddeleLine = "\033[32m╍\033[0m"; +std::string horizontalLine = "\033[34m═\033[0m"; +std::string verticalLine = "\033[34m║\033[0m"; + + +// ----- Functions prototype ----- + +void setup(); +void deallocation(); +void locatePaddle(int x); +void boardRender(); +void ballMoveTask(); +void endGame(); +void inputThread(); + +// ----- Main function ----- + +std::atomic running(true); /* used in order to manage the thread + and prevent threads racing (racing : a thread reading + a variable created by another when it's not fully written) */ +std::atomic paused(false); + +int main(){ + + using namespace std::chrono; + + show_console_cursor(false); // hides the cursor + + // Unicode settings + + system("chcp 65001"); // for showing the unicode characters in terminal + + // Menu + + while (true) { + system("clear"); + char opt = optionChoosenByUser(); + switch (opt) { + case '1': + { // written in scope to maintain the threads + + setup(); // sets up the board + + running.store(true); + + system("clear"); + std::cout << "\n\n Enter your name : "; + std::cin >> player.name; + + system("clear"); + + std::cout << "\n\n PRESS ANY KEY TO START THE GAME. \n"; + getch(); + + running = true; + + std::thread threadOne(boardRender); // creates a thread for board rendering + std::thread threadTwo(ballMoveTask); // created a thread for ball movement + std::thread threadThree(inputThread); + + system("clear"); + + while (running && !player.gameover && !player.won) { + // small sleep for threads to finish their jobs + std::this_thread::sleep_for(milliseconds(100)); + } + + running = false; + + threadOne.join(); + threadTwo.join(); + threadThree.detach(); + + if (player.gameover || player.won) { + system("clear"); + endGame(); + std::cout << "\n\n Press enter twice to return to main menu\n"; + getch(); + saveData(player.name, player.score); + } + + } + break; + case '2': + helpMenu(); + break; + case '3': + showHistory(); + break; + case '4': + system("clear"); + std::cout << std::endl; + std::cout << " Press \"c\" to confirm\n"; + char confirmChar = getch(); + if (confirmChar == 'c'){ + system("clear"); + return 0; + } + system("clear"); + break; + + } + } + +} + +// ----- Functions ----- + +void locatePaddle(int x){ // receives starting point x because y stays the same + board[board_width - 2][x] = paddeleLine; +} + +// setup the board when starting new game +void setup(){ + + /* + only the starting char of bricks and the paddle change. because it would be easier to layout them. + */ + + + // clearing board + + for (int i = 0; i < board_width; i++){ + for (int j = 0; j < board_lenght; j++) { + board[i][j] = " "; + } + } + + // setting up variables + + player.gameover = false; + player.won = false; + player.score = 0; + player.health = 3; + bricksCount = 36; + + // setting up bricks + /* + How it works : it goes in a row and when ever it reaches the start of a brick it places that into the bricks_idx and does the same thing for the y. + Note : x and y in this function are swapped compared to cartesian system. + */ + + int counterX = 1, counterY = 10; + for(int i = 0; i < 36; i++){ // saving the location of each brick + bricks[i].loc.x = counterX; + bricks[i].loc.y = counterY; + bricks_idx[i] = counterX; + bricks_idy[i] = counterY; + counterY += 5; + if(i == 11 || i == 23){ // 11 is where firsto row of bricks end and 23 is end of the second row + counterY = 10; + counterX++; + } + } + + // setting up brick scores + /* + first row(index 0 - 11) : 1000 + second row(index 12 - 23) : 500 + third row(index 24 - 35) : 200 + */ + for(int i = 0; i < 12; i++){ + bricks[i].score = 1000; + } + for(int i = 12; i < 24; i++){ + bricks[i].score = 500; + } + for(int i = 24; i < 36; i++){ + bricks[i].score = 200; + } + + // setting up borders + for(int i = 1; i < board_lenght - 1; i++){ + board[0][i] = horizontalLine; // top + board[board_width - 1][i] = horizontalLine; // bottom + } + for(int i = 1; i < board_width - 1; i++){ + board[i][0] = verticalLine; // right + board[i][board_lenght - 1] = verticalLine; // left + } + // setting up corners + board[0][0] = tlCorner; + board[0][board_lenght - 1] = trCorner; + board[board_width - 1][0] = blCorner; + board[board_width - 1][board_lenght - 1] = brCorner; + + // setting up bricks + for(int i = 1; i <= 3; i++){ + for(int j = 1; j < board_lenght - 1; j++){ + if(isBrick(i, j)){ + board[i][j] = block; + j += 4; + } + } + } + + // setup paddle + paddle.start_loc.x = (board_lenght - 10) / 2 - 1; + paddle.start_loc.y = board_width - 2; + locatePaddle(paddle.start_loc.x); + + + // setup ball : locations are chosen such that the ball hits the paddle initially + ball.loc.x = 22; + ball.loc.y = 15; + + /* Note : the velocity below is not suitable for configuring the speed + in order to change the speed, change the value of interval in ballMoveTask + changing the velocity below would affect the ball collision angle.*/ + + ball.v.vX = 1; + ball.v.vY = 1; + + board[ball.loc.y][ball.loc.x] = ballChar; + +} + +// Board rendering + +void boardRender() { + + auto interval = milliseconds(16); + auto next_time = steady_clock::now(); + + while (running) { + next_time += interval; + drawBoard(); + std::this_thread::sleep_until(next_time); + } + +} + +// Input processing thread + +void inputThread() { + while (running && !player.gameover && !player.won) { + int q = inputProccessing(paddle); + if (q == 0) { + running = false; + break; + } + } +} + +// Ball rendering + +void ballMoveTask(){ + + auto next_time = steady_clock::now(); + + while (running) { + auto interval = milliseconds(velocitySpeed[abs(ball.v.vX) - 1]); + next_time += interval; + + if (!paused){ + ballmover(ball); + if (player.gameover || player.won){ + running = false; // closes the threads + break; + } + } + std::this_thread::sleep_until(next_time); + } + +} + +void endGame(){ + + // Reporting + + if (player.gameover){ + std::cout << "\n\n Game over.\n"; + }else { + std::cout << "\n\n Game finished. \n Congrats! You Won!"; + } + + std::cout << "\n Your score : "; + std::cout << player.score; + +} diff --git a/src/history.txt b/src/history.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/screenshots/Linux/game.png b/src/screenshots/Linux/game.png new file mode 100644 index 0000000..345bfcc Binary files /dev/null and b/src/screenshots/Linux/game.png differ diff --git a/src/screenshots/Linux/gamehistory.png b/src/screenshots/Linux/gamehistory.png new file mode 100644 index 0000000..8b27ea3 Binary files /dev/null and b/src/screenshots/Linux/gamehistory.png differ diff --git a/src/screenshots/Linux/gameover.png b/src/screenshots/Linux/gameover.png new file mode 100644 index 0000000..6aba645 Binary files /dev/null and b/src/screenshots/Linux/gameover.png differ diff --git a/src/screenshots/Linux/help.png b/src/screenshots/Linux/help.png new file mode 100644 index 0000000..da12aa3 Binary files /dev/null and b/src/screenshots/Linux/help.png differ diff --git a/src/screenshots/Linux/mainmenu.png b/src/screenshots/Linux/mainmenu.png new file mode 100644 index 0000000..77685bd Binary files /dev/null and b/src/screenshots/Linux/mainmenu.png differ diff --git a/src/screenshots/Windows (WSL)/game.jpg b/src/screenshots/Windows (WSL)/game.jpg new file mode 100644 index 0000000..68fa0cb Binary files /dev/null and b/src/screenshots/Windows (WSL)/game.jpg differ diff --git a/src/screenshots/Windows (WSL)/game2.jpg b/src/screenshots/Windows (WSL)/game2.jpg new file mode 100644 index 0000000..7e2c329 Binary files /dev/null and b/src/screenshots/Windows (WSL)/game2.jpg differ diff --git a/src/screenshots/Windows (WSL)/menu.jpg b/src/screenshots/Windows (WSL)/menu.jpg new file mode 100644 index 0000000..f64bd44 Binary files /dev/null and b/src/screenshots/Windows (WSL)/menu.jpg differ diff --git a/src/screenshots/macOS/game.png b/src/screenshots/macOS/game.png new file mode 100644 index 0000000..1ef0760 Binary files /dev/null and b/src/screenshots/macOS/game.png differ diff --git a/src/screenshots/macOS/gamehistory.png b/src/screenshots/macOS/gamehistory.png new file mode 100644 index 0000000..d04e679 Binary files /dev/null and b/src/screenshots/macOS/gamehistory.png differ diff --git a/src/screenshots/macOS/helpmenu.png b/src/screenshots/macOS/helpmenu.png new file mode 100644 index 0000000..afe6752 Binary files /dev/null and b/src/screenshots/macOS/helpmenu.png differ