329 lines
8.3 KiB
C++
329 lines
8.3 KiB
C++
// 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 <cstdlib>
|
|
#include <string>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <atomic>
|
|
|
|
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<bool> 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<bool> 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;
|
|
|
|
}
|