After doing the Snake Game I decided to go more crazy and made an Arkanoid Clone:
Currently this only runs on Windows. I tested it under MSVC2017 with Windows 7. If you want to get it to run on Linux or Mac you have to implement the Part in Console.cpp
.
I organized the code as following:
GameObject: This class is the base for all objects in the game. It handles all the movements and also collisions between two
GameObjects
.Platform: Derived from
GameObject
. The only thing new here is that it can be reset to the init position. Used when a level is restarted.Wall: Derived from
GameObject
. Not moveable. Can be passed toBall
orPlatform
to handle collisions between each otherBall: Derived from
GameObject
. Can be deactivated so it does not move. This is used before the user hits spacebar to start the game. The move routine is override to add a gravity force on theBall
Brick: Derived from
GameObject
. Not moveable. Has hitpoints and can be flaged destroyed. This is used to not have the need to delete the Brick from the field it just becomes invisible. The init hitpoints are stored as well to be used for different scoringsLevel: Handles everything which happens in one played Level. It can take a collection of Bricks to load different levels.
Game: This routine calls the different levels and announces Game Over if it occurs
Grid: Used to display on the console. In the Background everything like Positions of the ball gets calculated with floating point operations. Since the console has very big pixels we only show whole sizes so 10.5 becomes 10 etc.
Console: Currently only supporting Windows. Commands to manipulate the console and check for keys pressed
MathHelper: Some defines and functions to convert from degree to radient. For readability in the GameObject calculations and debug purposes.
Time: Handles the recording of the passed time between two points for the calculations of the movements and also provides a wait function.
NearlyEqual: Function to compare double equal with delta similar to ints.
Let me know what can be improved. I want to add more features but first i want to know how is the state of the current code.
Some questions which maybe help to review
- Is it well organized?
- Would you have organized it different?
- How about the math of the collisions. Can it be improved?
- Are there any bad practices?
- Is stuff done to complicated
- Can you understand the code or is it hard to read through it?
Let me know. If you not have the time to read all pick a part and let me know what you think of it.
The Code:
main.cpp
#include "Game.h"
#include <iostream>
int main()
try {
while (arkanoid::runGame());
}
catch (const std::out_of_range& e) {
std::cerr << e.what() << "\n";
std::cin.get();
}
catch (const std::invalid_argument& e) {
std::cerr << e.what() << "\n";
std::cin.get();
}
catch (...) {
std::cerr << "unknown error " << "\n";
std::cin.get();
}
Game.h
#ifndef GAME_H
#define GAME_H
namespace arkanoid {
bool runGame();
bool userInputGameOver(long long score);
}
#endif
Game.cpp
#include "Game.h"
#include "Console.h"
#include "Level.h"
#include <iostream>
namespace arkanoid {
bool runGame()
{
console::resize(500, 453);
static constexpr auto startScore = 0;
static constexpr auto startLives = 3;
long long score = startScore;
int lives = startLives;
int level{ 0 };
for (;;) {
Level level1{ score, lives, ++level, makeBricksLevel1() };
level1.run();
score = level1.score();
lives = level1.lives();
if (level1.isGameOver()) {
return userInputGameOver(score);
}
Level level2{ level1.score(), level1.lives(), ++level, makeBricksLevel2() };
level2.run();
score = level1.score();
lives = level1.lives();
if (level2.isGameOver()) {
return userInputGameOver(score);
}
}
}
bool userInputGameOver(long long score)
{
console::clearScreen();
std::cout << "Game Over!!!\n"
<< "SCORE:" << score<<'\n'
<< "Press R To Try again or ESC to Quit\n";
for (;;) {
if (console::rKeyHoldDown()) {
return true;
}
if (console::escKeyHoldDown()) {
return false;
}
}
console::clearScreen();
}
} // namespace arkanoid
Level.h
#ifndef LEVEL_H
#define LEVEL_H
#include "Ball.h"
#include "Brick.h"
#include "Platform.h"
#include "Wall.h"
#include <vector>
namespace arkanoid {
class Wall;
class Brick;
class Platform;
class Level
{
public:
Level(long long score, int lives, int level, std::vector<Brick> bricks);
~Level() = default;
void run();
long long score() const;
int lives() const;
bool isGameOver() const;
private:
void handleBallMovementsAndCollisions(double elapsedTimeInMS);
void printToConsole();
Platform makePlatform();
Ball makeBall();
static constexpr int boardWidth{ 26 };
static constexpr int boardHeight{ 18 };
static constexpr auto plattformWidth{ 5.0 };
static constexpr auto wallThickness{ 1.0 };
static constexpr auto pointsPerBrick{ 100 };
long long mScore;
int mLives;
const int mLevel;
std::vector<Brick> mBricks;
const Wall mLeftWall;
const Wall mRightWall;
const Wall mTopWall;
Platform mPlatform;
Ball mBall;
bool mIsGameOver;
};
void movePlatformBetweenWalls(
Platform& platform, const Wall& leftWall, const Wall& rightWall,
double elapsedTimeInMS);
Point calculatePlattformInitPosition(
double plattformSize, double boardWidth, double boardHeight);
Point calculateBallInitPosition(double boardWidth, double boardHeight);
bool allBricksAreDestroyed(const std::vector<Brick>& bricks);
std::vector<Brick> makeBricksLevel1();
std::vector<Brick> makeBricksLevel2();
} // namespace arkanoid
#endif
Level.cpp
#include "Level.h"
#include "Console.h"
#include "Grid.h"
#include "Time.h"
#include <algorithm>
#include <iomanip>
#include <iostream>
namespace arkanoid {
Level::Level(long long score, int lives, int level,
const std::vector<Brick> bricks)
: mScore{score}, mLives{lives}, mLevel{level},
mBricks{bricks},
mLeftWall{
Point{0,0},
wallThickness, boardHeight
},
mRightWall{
Point{boardWidth - wallThickness, 0},
wallThickness, boardHeight
},
mTopWall{
Point{wallThickness, 0},
boardWidth - 2.0 * wallThickness, wallThickness
},
mPlatform{makePlatform()},
mBall{makeBall()},
mIsGameOver{false}
{
}
void Level::run()
{
std::chrono::time_point<std::chrono::high_resolution_clock> t1;
std::chrono::time_point<std::chrono::high_resolution_clock> t2;
double elapsedTimeInMS{ 0.0 };
for (;;) {
t1 = getCurrentTime();
if (!mBall.isActive() && console::spaceKeyHoldDown()) {
mBall.activate();
}
movePlatformBetweenWalls(
mPlatform, mLeftWall, mRightWall, elapsedTimeInMS);
if (mBall.isActive()) {
handleBallMovementsAndCollisions(elapsedTimeInMS);
}
if (allBricksAreDestroyed(mBricks)) {
return;
}
if (mIsGameOver) {
return;
}
printToConsole();
t2 = getCurrentTime();
elapsedTimeInMS = arkanoid::getElapsedTime(t1, t2);
}
}
long long Level::score() const
{
return mScore;
}
int Level::lives() const
{
return mLives;
}
bool Level::isGameOver() const
{
return mIsGameOver;
}
void Level::handleBallMovementsAndCollisions(double elapsedTimeInMS)
{
if (mBall.reflectIfHit(mRightWall)) {
;
}
else if (mBall.reflectIfHit(mLeftWall)) {
;
}
else if (mBall.reflectIfHit(mTopWall)) {
;
}
else if (mBall.reflectIfHit(mPlatform)) {
;
}
else if (mBall.bottomRight().y >= boardHeight) {
wait(std::chrono::milliseconds{ 1000 });
--mLives;
if (mLives == 0) {
mIsGameOver = true;
}
mBall.deactivate();
mPlatform.setToInitPosition();
return;
}
for (auto& brick : mBricks) {
if (brick.isDestroyed()) {
continue;
}
if (mBall.reflectIfHit(brick)) {
brick.decreaseHitpoints();
if (brick.isDestroyed()) {
mScore +=
pointsPerBrick * brick.startHitpoints();
}
break;
}
}
mBall.move(elapsedTimeInMS);
}
void Level::printToConsole()
{
console::putCursorToStartOfConsole();
Grid grid(
static_cast<int>(boardWidth),
static_cast<int>(boardHeight)
);
grid.add(mBall);
grid.add(mPlatform);
grid.add(mLeftWall);
grid.add(mRightWall);
grid.add(mTopWall);
for (const auto& brick : mBricks) {
grid.add(brick);
}
console::putCursorToStartOfConsole();
std::cout << "Score:" << std::setw(15) << mScore << " "
<< "Lives:" << std::setw(4) << mLives << " "
<< "Level:" << std::setw(4) << mLevel << '\n'
<< grid << '\n';
//std::cout << std::setw(5) << mBall.topLeft().x << " "
// << std::setw(5) << mBall.topLeft().y << " "
// << std::setw(10) << radientToDegrees(mBall.angle())
// << std::setw(10) << radientToDegrees(mBall.quadrantAngle())
// << std::setw(3) << static_cast<int>(mBall.quadrant()) + 1 << '\n';
//std::cout << std::setw(5) << mPlatform.topLeft().x << " "
// << std::setw(5) << mPlatform.topLeft().y << " "
// << std::setw(10) << radientToDegrees(mPlatform.angle())
// << std::setw(10) << radientToDegrees(mPlatform.quadrantAngle())
// << std::setw(3) << static_cast<int>(mPlatform.quadrant()) + 1 << '\n';
}
Platform Level::makePlatform()
{
return Platform{
calculatePlattformInitPosition(
plattformWidth, boardWidth, boardHeight),
static_cast<double>(boardWidth),
plattformWidth,
};
}
Ball Level::makeBall()
{
return Ball{ calculateBallInitPosition(boardWidth, boardHeight),
static_cast<double>(boardWidth),
static_cast<double>(boardHeight)
};
}
void movePlatformBetweenWalls(
Platform& platform, const Wall& leftWall, const Wall& rightWall,
double elapsedTimeInMS)
{
if (console::rightKeyHoldDown()) {
platform.setAngle(0.0);
platform.move(elapsedTimeInMS);
}
else if (console::leftKeyHoldDown()) {
platform.setAngle(deg_180);
platform.move(elapsedTimeInMS);
}
if (platform.reflectIfHit(rightWall)) {
;
}
else if (platform.reflectIfHit(leftWall)) {
;
}
}
Point calculatePlattformInitPosition(
double plattformSize, double boardWidth, double boardHeight)
{
return Point{
boardWidth / 2.0 - plattformSize / 2.0,
boardHeight - 3.0
};
}
Point calculateBallInitPosition(double boardWidth, double boardHeight)
{
return Point{
boardWidth / 2.0 - 1,
boardHeight - 4.0
};
}
bool allBricksAreDestroyed(const std::vector<Brick>& bricks)
{
return std::find_if(bricks.begin(), bricks.end(),
[](const Brick& b)
{
return !b.isDestroyed();
}) == bricks.end();
}
std::vector<Brick> makeBricksLevel1()
{
constexpr auto brickLength = 3.0;
constexpr auto brickHeight = 1.0;
return std::vector<Brick>
{
Brick{ Point{4,2},brickLength,brickHeight,1 },
Brick{ Point{7,2},brickLength,brickHeight,1 },
Brick{ Point{10,2},brickLength,brickHeight,1 },
Brick{ Point{13,2},brickLength,brickHeight,1 },
Brick{ Point{16,2},brickLength,brickHeight,1 },
Brick{ Point{19,2},brickLength,brickHeight,1 },
Brick{ Point{4,3},brickLength,brickHeight,1 },
Brick{ Point{19,3},brickLength,brickHeight,1 },
Brick{ Point{4,4},brickLength,brickHeight,1 },
Brick{ Point{10,4},brickLength,brickHeight,2 },
Brick{ Point{13,4},brickLength,brickHeight,2 },
Brick{ Point{19,4},brickLength,brickHeight,1 },
Brick{ Point{4,5},brickLength,brickHeight,1 },
Brick{ Point{19,5},brickLength,brickHeight,1 },
Brick{ Point{4,6},brickLength,brickHeight,1 },
Brick{ Point{7,6},brickLength,brickHeight,1 },
Brick{ Point{10,6},brickLength,brickHeight,1 },
Brick{ Point{13,6},brickLength,brickHeight,1 },
Brick{ Point{16,6},brickLength,brickHeight,1 },
Brick{ Point{19,6},brickLength,brickHeight,1 },
};
}
std::vector<Brick> makeBricksLevel2()
{
constexpr auto brickLength = 3.0;
constexpr auto brickHeight = 1.0;
return std::vector<Brick>
{
// draw a C
Brick{ Point{4,2},brickLength,brickHeight,1 },
Brick{ Point{7,2},brickLength,brickHeight,1 },
Brick{ Point{10,2},brickLength,brickHeight,1 },
Brick{ Point{13,2},brickLength,brickHeight,1 },
Brick{ Point{4,3},brickLength,brickHeight,1 },
Brick{ Point{4,4},brickLength,brickHeight,1 },
Brick{ Point{4,5},brickLength,brickHeight,1 },
Brick{ Point{4,6},brickLength,brickHeight,1 },
Brick{ Point{4,7},brickLength,brickHeight,1 },
Brick{ Point{4,8},brickLength,brickHeight,1 },
Brick{ Point{4,9},brickLength,brickHeight,1 },
Brick{ Point{4,10},brickLength,brickHeight,1 },
Brick{ Point{7,10},brickLength,brickHeight,1 },
Brick{ Point{10,10},brickLength,brickHeight,1 },
Brick{ Point{13,10},brickLength,brickHeight,1 },
// draw first +
Brick{ Point{13,6},brickHeight,brickHeight,2 },
Brick{ Point{14,6},brickHeight,brickHeight,3 },
Brick{ Point{15,4},brickHeight,brickHeight,2 },
Brick{ Point{15,5},brickHeight,brickHeight,3 },
Brick{ Point{15,6},brickHeight,brickHeight,4 },
Brick{ Point{15,7},brickHeight,brickHeight,3 },
Brick{ Point{15,8},brickHeight,brickHeight,2 },
Brick{ Point{16,6},brickHeight,brickHeight,3 },
Brick{ Point{17,6},brickHeight,brickHeight,2 },
// draw second +
Brick{ Point{19,6},brickHeight,brickHeight,5 },
Brick{ Point{20,6},brickHeight,brickHeight,6 },
Brick{ Point{21,4},brickHeight,brickHeight,5 },
Brick{ Point{21,5},brickHeight,brickHeight,3 },
Brick{ Point{21,6},brickHeight,brickHeight,7 },
Brick{ Point{21,7},brickHeight,brickHeight,6 },
Brick{ Point{21,8},brickHeight,brickHeight,5 },
Brick{ Point{22,6},brickHeight,brickHeight,6 },
Brick{ Point{23,6},brickHeight,brickHeight,5 },
};
}
} // namespace arkanoid
GameObject.h
#ifndef GAMEOBJECT_H
#define GAMEOBJECT_H
#include "Point.h"
namespace arkanoid {
enum class Quadrant {
I,
II,
III,
IV
};
class GameObject
{
public:
GameObject(
Point topLeft, double maxPositioX, double maxPositionY,
double width, double height,
double velocity, double angle);
virtual ~GameObject() = 0;
Point topLeft() const;
void setTopLeft(Point topLeft);
Point bottomRight() const;
double velocity() const;
void setVelocity(double velocity);
double angle() const;
void setAngle(double angle);
double quadrantAngle() const;
Quadrant quadrant() const;
double width() const;
double height() const;
virtual void move(double elapsedTimeInMS);
bool reflectIfHit(const GameObject& obj);
private:
void reflectFromQuadrantOneIfHit(const GameObject& obj);
void reflectFromQuadrantTwoIfHit(const GameObject& obj);
void reflectFromQuadrantThreeIfHit(const GameObject& obj);
void reflectFromQuadrantFourIfHit(const GameObject& obj);
void reflectToQuadrantFourIfIntersectsWithX(const GameObject& obj);
void reflectToQuadrantTwoIfIntersectsWithY(const GameObject& obj);
void reflectToQuadrantThreeIfIntersectsWithX(const GameObject& obj);
void reflectToQuadrantOneIfIntersectsWithY(const GameObject& obj);
void reflectToQuadrantTwoIfIntersectsWithX(const GameObject& obj);
void reflectToQuadrantFourIfIntersectsWithY(const GameObject& obj);
void reflectToQuadrantOneIfIntersectsWithX(const GameObject& obj);
void reflectToQuadrantThreeIfIntersectsWithY(const GameObject& obj);
void toQuadrantOne();
void toQuadrantTwo();
void toQuadrantThree();
void toQuadrantFour();
Point mTopLeft;
const double mMaxPositionX;
const double mMaxPositionY;
const double mWidth;
const double mHeight;
double mVelocity;
Quadrant mQuadrant;
double mQuadrantAngle;
};
bool isInQuadrantOne(double angle);
bool isInQuadrantTwo(double angle);
bool isInQuadrantThree(double angle);
bool isInQuadrantFour(double angle);
bool interectsWithRightX(const GameObject& a, const GameObject& b);
void putBeforeIntersectsWithRightX(GameObject& a, const GameObject& b);
bool interectsWithLeftX(const GameObject& a, const GameObject& b);
void putBeforeIntersectsWithLeftX(GameObject& a, const GameObject& b);
bool interectsWithBottomY(const GameObject& a, const GameObject& b);
void putBeforeIntersectsWithBottomY(GameObject& a, const GameObject& b);
bool interectsWithTopY(const GameObject& a, const GameObject& b);
void putBeforeIntersectsWithTopY(GameObject& a, const GameObject& b);
bool isInsideWithY(const GameObject& a, const GameObject& b);
bool isInsideWithX(const GameObject& a, const GameObject& b);
bool intersectsFromRigthWithX(const GameObject& a, const GameObject& b);
bool intersectsFromLeftWithX(const GameObject& a, const GameObject& b);
bool intersectsFromTopWithY(const GameObject& a, const GameObject& b);
bool intersectsFromBottomWithY(const GameObject& a, const GameObject& b);
Point calcDelta(double quadrantAngle, Quadrant quadrant, double sideC);
double calcTraveldWay(double deltaTimeMS, double velocityInS);
double calcAlphaIfOver360(double alpha);
Quadrant calcQuadrant(double alpha);
double angleToQuadrantAngle(double angle, Quadrant quadrant);
double qudrantAngleToAngle(double quadrantAngle, Quadrant quadrant);
double increaseAngle(double quadrantAngle);
double decreaseAngle(double quadrantAngle);
double mirror(double quadrantAngle);
} // namespace arkanoid
#endif
GameObject.cpp
#include "GameObject.h"
#include "MathHelper.h"
#include "NearlyEqual.h"
#include <algorithm>
#include <stdexcept>
#include <tuple>
namespace arkanoid {
GameObject::GameObject(Point topLeft,
double maxPositioX, double maxPositionY,
double width, double height,
double velocity, double angle)
:mTopLeft{ topLeft },
mMaxPositionX(maxPositioX),
mMaxPositionY(maxPositionY),
mWidth{ width },
mHeight{ height },
mVelocity{ velocity },
mQuadrant{ calcQuadrant(angle) },
mQuadrantAngle{ angleToQuadrantAngle(angle, mQuadrant) }
{
}
GameObject::~GameObject() = default;
Point GameObject::topLeft() const
{
return mTopLeft;
}
void GameObject::setTopLeft(Point topLeft)
{
mTopLeft = topLeft;
}
Point GameObject::bottomRight() const
{
return Point{ mTopLeft.x + mWidth , mTopLeft.y + mHeight };
}
double GameObject::velocity() const
{
return mVelocity;
}
void GameObject::setVelocity(double velocity)
{
mVelocity = velocity;
}
double GameObject::angle() const
{
return qudrantAngleToAngle(mQuadrantAngle, mQuadrant);
}
void GameObject::setAngle(double angle)
{
angle = calcAlphaIfOver360(angle);
mQuadrant = calcQuadrant(angle);
mQuadrantAngle = angleToQuadrantAngle(angle, mQuadrant);
}
double GameObject::quadrantAngle() const
{
return mQuadrantAngle;
}
Quadrant GameObject::quadrant() const
{
return mQuadrant;
}
double GameObject::width() const
{
return mWidth;
}
double GameObject::height() const
{
return mHeight;
}
void GameObject::move(double elapsedTimeInMS)
{
auto distance = calcTraveldWay(elapsedTimeInMS, mVelocity);
auto traveldWay = calcDelta(mQuadrantAngle, mQuadrant, distance);
mTopLeft.x += traveldWay.x;
mTopLeft.y += traveldWay.y;
auto maxX = mTopLeft.x + mWidth;
mTopLeft.x = std::clamp(maxX, mWidth, mMaxPositionX) - mWidth;
auto maxY = mTopLeft.y + mHeight;
mTopLeft.y = std::clamp(maxY, mHeight, mMaxPositionY) - mHeight;
}
bool GameObject::reflectIfHit(const GameObject& obj)
{
auto oldQuadrant = mQuadrant;
switch (mQuadrant)
{
case Quadrant::I:
reflectFromQuadrantOneIfHit(obj);
break;
case Quadrant::II:
reflectFromQuadrantTwoIfHit(obj);
break;
case Quadrant::III:
reflectFromQuadrantThreeIfHit(obj);
break;
case Quadrant::IV:
reflectFromQuadrantFourIfHit(obj);
break;
}
return mQuadrant != oldQuadrant;
}
void GameObject::reflectFromQuadrantOneIfHit(const GameObject& obj)
{
if (interectsWithBottomY(*this, obj)) {
reflectToQuadrantFourIfIntersectsWithX(obj);
}
else if (interectsWithRightX(*this, obj)) {
reflectToQuadrantTwoIfIntersectsWithY(obj);
}
}
void GameObject::reflectFromQuadrantTwoIfHit(const GameObject& obj)
{
if (interectsWithLeftX(*this, obj)) {
reflectToQuadrantOneIfIntersectsWithY(obj);
}
else if (interectsWithBottomY(*this, obj)) {
reflectToQuadrantThreeIfIntersectsWithX(obj);
}
}
void GameObject::reflectFromQuadrantThreeIfHit(const GameObject& obj)
{
if (interectsWithLeftX(*this, obj)) {
reflectToQuadrantFourIfIntersectsWithY(obj);
}
else if (interectsWithTopY(*this, obj)) {
reflectToQuadrantTwoIfIntersectsWithX(obj);
}
}
void GameObject::reflectFromQuadrantFourIfHit(const GameObject& obj)
{
if (interectsWithRightX(*this, obj)) {
reflectToQuadrantThreeIfIntersectsWithY(obj);
}
else if (interectsWithTopY(*this, obj)) {
reflectToQuadrantOneIfIntersectsWithX(obj);
}
}
void GameObject::reflectToQuadrantFourIfIntersectsWithX(
const GameObject& obj)
{
if (isInsideWithX(*this, obj)) {
toQuadrantFour();
}
else if (intersectsFromRigthWithX(*this, obj) ||
intersectsFromLeftWithX(*this, obj)) {
toQuadrantFour();
mQuadrantAngle = increaseAngle(mQuadrantAngle);
}
else {
return;
}
putBeforeIntersectsWithBottomY(*this, obj);
}
void GameObject::reflectToQuadrantTwoIfIntersectsWithY(
const GameObject& obj)
{
if (isInsideWithY(*this, obj)) {
toQuadrantTwo();
}
else if (intersectsFromTopWithY(*this, obj) ||
intersectsFromBottomWithY(*this, obj)) {
toQuadrantTwo();
mQuadrantAngle = increaseAngle(mQuadrantAngle);
}
else {
return;
}
putBeforeIntersectsWithRightX(*this, obj);
}
void GameObject::reflectToQuadrantThreeIfIntersectsWithX(
const GameObject& obj)
{
if (isInsideWithX(*this, obj)) {
toQuadrantThree();
}
else if (intersectsFromRigthWithX(*this, obj) ||
intersectsFromLeftWithX(*this, obj)) {
toQuadrantThree();
mQuadrantAngle = decreaseAngle(mQuadrantAngle);
}
else {
return;
}
putBeforeIntersectsWithBottomY(*this, obj);
}
void GameObject::reflectToQuadrantOneIfIntersectsWithY(
const GameObject& obj)
{
if (isInsideWithY(*this, obj)) {
toQuadrantOne();
}
else if (intersectsFromTopWithY(*this, obj) ||
intersectsFromBottomWithY(*this, obj)) {
toQuadrantOne();
mQuadrantAngle = decreaseAngle(mQuadrantAngle);
}
else {
return;
}
putBeforeIntersectsWithLeftX(*this, obj);
}
void GameObject::reflectToQuadrantTwoIfIntersectsWithX(
const GameObject& obj)
{
if (isInsideWithX(*this, obj)) {
toQuadrantTwo();
}
else if (intersectsFromRigthWithX(*this, obj) ||
intersectsFromLeftWithX(*this, obj)) {
toQuadrantTwo();
mQuadrantAngle = increaseAngle(mQuadrantAngle);
}
else {
return;
}
putBeforeIntersectsWithTopY(*this, obj);
}
void GameObject::reflectToQuadrantFourIfIntersectsWithY(
const GameObject& obj)
{
if (isInsideWithY(*this, obj)) {
toQuadrantFour();
}
else if (intersectsFromTopWithY(*this, obj) ||
intersectsFromBottomWithY(*this, obj)) {
toQuadrantFour();
mQuadrantAngle = increaseAngle(mQuadrantAngle);
}
else {
return;
}
putBeforeIntersectsWithLeftX(*this, obj);
}
void GameObject::reflectToQuadrantOneIfIntersectsWithX(
const GameObject& obj)
{
if (isInsideWithX(*this, obj)) {
toQuadrantOne();
}
else if (intersectsFromRigthWithX(*this, obj) ||
intersectsFromLeftWithX(*this, obj)) {
toQuadrantOne();
mQuadrantAngle = decreaseAngle(mQuadrantAngle);
}
else {
return;
}
putBeforeIntersectsWithTopY(*this, obj);
}
void GameObject::reflectToQuadrantThreeIfIntersectsWithY(
const GameObject& obj)
{
if (isInsideWithY(*this, obj)) {
toQuadrantThree();
}
else if (intersectsFromTopWithY(*this, obj) ||
intersectsFromBottomWithY(*this, obj)) {
toQuadrantThree();
mQuadrantAngle = decreaseAngle(mQuadrantAngle);
}
else {
return;
}
putBeforeIntersectsWithRightX(*this, obj);
}
void GameObject::toQuadrantOne()
{
mQuadrant = Quadrant::I;
mQuadrantAngle = mirror(mQuadrantAngle);
}
void GameObject::toQuadrantTwo()
{
mQuadrant = Quadrant::II;
mQuadrantAngle = mirror(mQuadrantAngle);
}
void GameObject::toQuadrantThree()
{
mQuadrant = Quadrant::III;
mQuadrantAngle = mirror(mQuadrantAngle);
}
void GameObject::toQuadrantFour()
{
mQuadrant = Quadrant::IV;
mQuadrantAngle = mirror(mQuadrantAngle);
}
bool isInQuadrantOne(double angle)
{
return angle >= deg_0 && angle <= deg_90;
}
bool isInQuadrantTwo(double angle)
{
return angle > deg_90 && angle <= deg_180;
}
bool isInQuadrantThree(double angle)
{
return angle > deg_180 && angle <= deg_270;
}
bool isInQuadrantFour(double angle)
{
return angle > deg_270 && angle <= deg_360;
}
bool interectsWithRightX(const GameObject& a, const GameObject& b)
{
return a.bottomRight().x >= b.topLeft().x &&
a.topLeft().x < b.topLeft().x;
}
void putBeforeIntersectsWithRightX(GameObject& a, const GameObject& b)
{
Point p = a.topLeft();
p.x = b.topLeft().x - a.width();
a.setTopLeft(p);
}
bool interectsWithLeftX(const GameObject& a, const GameObject& b)
{
return a.topLeft().x <= b.bottomRight().x &&
a.bottomRight().x > b.bottomRight().x;
}
void putBeforeIntersectsWithLeftX(GameObject& a, const GameObject& b)
{
Point p = a.topLeft();
p.x = b.bottomRight().x;
a.setTopLeft(p);
}
bool interectsWithBottomY(const GameObject& a, const GameObject& b)
{
return a.bottomRight().y >= b.topLeft().y &&
a.topLeft().y < b.topLeft().y;
}
void putBeforeIntersectsWithBottomY(GameObject& a, const GameObject& b)
{
Point p = a.topLeft();
p.y = b.topLeft().y - a.height();
a.setTopLeft(p);
}
bool interectsWithTopY(const GameObject& a, const GameObject& b)
{
return a.topLeft().y <= b.bottomRight().y &&
a.bottomRight().y > b.bottomRight().y;
}
void putBeforeIntersectsWithTopY(GameObject& a, const GameObject& b)
{
Point p = a.topLeft();
p.y = b.bottomRight().y;
a.setTopLeft(p);
}
bool isInsideWithY(const GameObject& a, const GameObject& b)
{
return a.topLeft().y >= b.topLeft().y &&
a.bottomRight().y <= b.bottomRight().y;
}
bool isInsideWithX(const GameObject& a, const GameObject& b)
{
return a.topLeft().x >= b.topLeft().x &&
a.bottomRight().x <= b.bottomRight().x;
}
bool intersectsFromRigthWithX(const GameObject& a, const GameObject& b)
{
return a.bottomRight().x >= b.topLeft().x &&
a.bottomRight().x <= b.bottomRight().x &&
a.topLeft().x < b.topLeft().x;
}
bool intersectsFromLeftWithX(const GameObject& a, const GameObject& b)
{
return a.topLeft().x >= b.topLeft().x &&
a.topLeft().x <= b.bottomRight().x &&
a.bottomRight().x > b.bottomRight().x;
}
bool intersectsFromTopWithY(const GameObject& a, const GameObject& b)
{
return a.bottomRight().y >= b.topLeft().y &&
a.bottomRight().y <= b.bottomRight().y &&
a.topLeft().y < b.topLeft().y;
}
bool intersectsFromBottomWithY(const GameObject& a, const GameObject& b)
{
return a.topLeft().y >= b.topLeft().y &&
a.topLeft().y <= b.bottomRight().y &&
a.bottomRight().y > b.bottomRight().y;
}
Point calcDelta(double quadrantAngle, Quadrant quadrant, double sideC)
{
if (nearlyEqual(sideC, 0.0)) {
return Point{ 0,0 };
}
auto sideA = sin(quadrantAngle) * sideC;
auto sideB = cos(quadrantAngle) * sideC;
Point ret;
switch (quadrant)
{
case Quadrant::I:
ret.x = sideB;
ret.y = sideA;
break;
case Quadrant::II:
ret.x = -sideA;
ret.y = sideB;
break;
case Quadrant::III:
ret.x = -sideB;
ret.y = -sideA;
break;
case Quadrant::IV:
ret.x = sideA;
ret.y = -sideB;
break;
}
return ret;
}
double calcTraveldWay(double deltaTimeMS, double velocityInS)
{
return deltaTimeMS / 1000.0 * velocityInS;
}
double calcAlphaIfOver360(double alpha)
{
if (alpha > deg_360) {
alpha -= deg_360;
}
return alpha;
}
Quadrant calcQuadrant(double alpha)
{
if (isInQuadrantOne(alpha)) {
return Quadrant::I;
}
if (isInQuadrantTwo(alpha)) {
return Quadrant::II;
}
if (isInQuadrantThree(alpha)) {
return Quadrant::III;
}
return Quadrant::IV;
}
double angleToQuadrantAngle(double angle, Quadrant quadrant)
{
return angle - deg_90 * static_cast<int>(quadrant);
}
double qudrantAngleToAngle(double quadrantAngle, Quadrant quadrant)
{
return quadrantAngle + deg_90 * static_cast<int>(quadrant);
}
double increaseAngle(double quadrantAngle)
{
quadrantAngle *= 1.03;
return std::clamp(quadrantAngle, 0.0, deg_60);
}
double decreaseAngle(double quadrantAngle)
{
return quadrantAngle * 0.97;
}
double mirror(double quadrantAngle)
{
return deg_90 - quadrantAngle;
}
} // namespace arkanoid
Ball.h
#ifndef BALL_H
#define BALL_H
#include "GameObject.h"
#include "MathHelper.h"
namespace arkanoid {
class Ball : public GameObject
{
public:
Ball(Point topLeft, double maxPositionX, double maxPositionY);
~Ball() override = default;
bool isActive();
void activate();
void deactivate();
void move(double elapsedTimeInMS) override;
private:
const Point mInitPosition;
bool mIsActive;
static constexpr auto startAngle{ deg_135 };
static constexpr auto startVelocity{ 9.0 };
static constexpr auto gravityVelocity{ 2.5 };
static constexpr auto width{ 1 };
static constexpr auto height{ 1 };
};
} // namespace arkanoid
#endif
Ball.cpp
#include "Ball.h"
#include <algorithm>
namespace arkanoid {
Ball::Ball(Point topLeft, double maxPositionX, double maxPositionY)
:GameObject(topLeft,
maxPositionX, maxPositionY, width, height, 0.0, 0.0),
mInitPosition{ topLeft },
mIsActive{false}
{
}
bool Ball::isActive()
{
return mIsActive;
}
void Ball::deactivate()
{
mIsActive = false;
setAngle(0.0);
setVelocity(0.0);
setTopLeft(mInitPosition);
}
void Ball::activate()
{
mIsActive = true;
setAngle(startAngle);
setVelocity(startVelocity);
}
void Ball::move(double elapsedTimeInMS)
{
auto distanceY = calcTraveldWay(elapsedTimeInMS, gravityVelocity);
auto p = topLeft();
p.y += distanceY;
setTopLeft(p);
GameObject::move(elapsedTimeInMS);
}
} // namespace arkanoid
Brick.h
#ifndef BRICK_H
#define BRICK_H
#include "GameObject.h"
namespace arkanoid {
class Brick : public GameObject
{
public:
Brick(Point topLeft, double width, double height, std::size_t hitpoints);
~Brick() override = default;
double velocity() const = delete;
void setVelocity(double velocity) = delete;
double angle() const = delete;
void setAngle(double angle) = delete;
void move(const Point& delta) = delete;
std::size_t startHitpoints() const;
std::size_t hitpoints() const;
void decreaseHitpoints();
bool isDestroyed() const;
private:
const std::size_t mStartHitpoints;
std::size_t mHitpoints;
};
} // namespace arkanoid
#endif
Brick.cpp
#include "Brick.h"
namespace arkanoid {
Brick::Brick(Point topLeft,
double width, double height, std::size_t hitpoints)
:GameObject(topLeft,
topLeft.x + width, topLeft.y + height,
width, height, 0, 0),
mHitpoints(hitpoints),
mStartHitpoints(hitpoints)
{
}
std::size_t Brick::startHitpoints() const
{
return mStartHitpoints;
}
std::size_t Brick::hitpoints() const
{
return mHitpoints;
}
void Brick::decreaseHitpoints()
{
--mHitpoints;
}
bool Brick::isDestroyed() const
{
return mHitpoints == 0;
}
} // namespace arkanoid
Platform.h
#ifndef PLATFORM_H
#define PLATFORM_H
#include "GameObject.h"
namespace arkanoid {
class Platform : public GameObject
{
public:
Platform(Point topLeft, double maxPositioX, double length);
~Platform() override = default;
void setToInitPosition();
private:
const Point mInitPosition;
static constexpr auto mStartAngle{ 0 };
static constexpr auto mStartVelocity{ 10.0 };
};
} // namespace arkanoid
#endif
Platform.cpp
#include "Platform.h"
namespace arkanoid {
Platform::Platform(
Point topLeft, double maxPositioX, double length)
: GameObject(
topLeft, maxPositioX, topLeft.y + 1,
length, 1, mStartVelocity, mStartAngle),
mInitPosition{ topLeft }
{
}
void Platform::setToInitPosition()
{
setTopLeft(mInitPosition);
}
} // namespace arkanoid
Wall.h
#ifndef WALL_H
#define WALL_H
#include "GameObject.h"
namespace arkanoid {
class Wall : public GameObject
{
public:
Wall(Point topLeft, double width, double height);
~Wall() override = default;
double velocity() const = delete;
void setVelocity(double velocity) = delete;
double angle() const = delete;
void setAngle(double angle) = delete;
void move(const Point& delta) = delete;
};
} // namespace arkanoid
#endif
Wall.cpp
#include "Wall.h"
namespace arkanoid {
Wall::Wall(Point topLeft, double width, double height)
:GameObject(topLeft,
topLeft.x + width, topLeft.y + height,
width, height, 0, 0)
{
}
}
Point.h
#ifndef POINT_H
#define POINT_H
#include <cstddef>
namespace arkanoid {
struct Point {
double x{ 0.0 };
double y{ 0.0 };
};
}// namespace arkanoid
#endif
Time.h
#ifndef TIME_H
#define TIME_H
#include <chrono>
namespace arkanoid {
std::chrono::time_point<std::chrono::high_resolution_clock>
getCurrentTime();
double getElapsedTime(
const std::chrono::time_point<
std::chrono::high_resolution_clock>& first,
const std::chrono::time_point<
std::chrono::high_resolution_clock>& last);
void wait(const std::chrono::milliseconds& milliseconds);
} // namespace arkanoid
#endif
Time.cpp
#include "Time.h"
#include <thread>
namespace arkanoid {
std::chrono::time_point<std::chrono::high_resolution_clock>
getCurrentTime()
{
return std::chrono::high_resolution_clock::now();
}
double getElapsedTime(
const std::chrono::time_point<
std::chrono::high_resolution_clock>& first,
const std::chrono::time_point<
std::chrono::high_resolution_clock>& last)
{
return static_cast<int>(
std::chrono::duration<double, std::milli>(last - first).count());
}
void wait(const std::chrono::milliseconds& milliseconds)
{
std::this_thread::sleep_for(milliseconds);
}
}
Grid.h
#ifndef GRID_H
#define GRID_H
#include <iosfwd>
#include <vector>
namespace arkanoid {
class Ball;
class Brick;
class Platform;
class Wall;
class GameObject;
enum class Field {
ball,
brick1Hits,
brick2Hits,
brick3Hits,
brick4Hits,
brick5Hits,
brick6Hits,
brick7Hits,
brick8Hits,
brick9Hits,
empty,
plattform,
wall
};
class Grid
{
public:
Grid(std::size_t width, std::size_t height);
~Grid() = default;
void add(const Ball& ball);
void add(const Brick& brick);
void add(const Platform& plattform);
void add(const Wall& wall);
private:
void add(const GameObject& gameObject, const Field& field);
std::vector<std::vector<Field>> mFields;
friend std::ostream& operator<<(std::ostream& os, const Grid& obj);
};
std::vector<std::vector<Field>> init(
std::size_t width, std::size_t height);
std::ostream& operator<<(std::ostream& os, const Grid& obj);
} // namespace arkanoid
#endif
Grid.cpp
#include "Grid.h"
#include "Ball.h"
#include "Brick.h"
#include "Platform.h"
#include "Wall.h"
#include <algorithm>
#include <cmath>
#include <iostream>
namespace arkanoid {
Grid::Grid(std::size_t width, std::size_t height)
: mFields{ init(width, height) }
{
}
void Grid::add(const Ball& ball)
{
add(ball, Field::ball);
}
void Grid::add(const Brick& brick)
{
if (brick.isDestroyed()) {
return;
}
auto brickType = static_cast<int>(Field::brick1Hits);
brickType = brickType + brick.hitpoints() - 1;
add(brick, static_cast<Field>(brickType));
}
void Grid::add(const Platform& plattform)
{
add(plattform, Field::plattform);
}
void Grid::add(const Wall& wall)
{
add(wall, Field::wall);
}
void Grid::add(const GameObject& gameObject, const Field& field)
{
auto x_begin =
static_cast<std::size_t>(gameObject.topLeft().x);
auto x_end =
static_cast<std::size_t>(gameObject.bottomRight().x);
auto y_begin =
static_cast<std::size_t>(gameObject.topLeft().y);
auto y_end =
static_cast<std::size_t>(gameObject.bottomRight().y);
for (auto y = y_begin; y < y_end; ++y) {
for (auto x = x_begin; x < x_end; ++x) {
mFields.at(y).at(x) = field;
}
}
}
std::vector<std::vector<Field>> init(std::size_t width, std::size_t height)
{
std::vector<Field> row(width, Field::empty);
std::vector<std::vector<Field>> fields(height, row);
return fields;
}
std::ostream& operator<<(std::ostream& os, const Grid& obj)
{
auto symbolWall = "# ";
auto symbolBall = "O ";
auto symbolBrick = "1 ";
auto symbolPlattform = "= ";
auto symbolEmpty = ". ";
auto symbolBrick1Hit = "1 ";
auto symbolBrick2Hit = "2 ";
auto symbolBrick3Hit = "3 ";
auto symbolBrick4Hit = "4 ";
auto symbolBrick5Hit = "5 ";
auto symbolBrick6Hit = "6 ";
auto symbolBrick7Hit = "7 ";
auto symbolBrick8Hit = "8 ";
auto symbolBrick9Hit = "9 ";
auto size_y = obj.mFields.size();
auto size_x = obj.mFields.at(0).size();
for (const auto& row : obj.mFields) {
for (const auto& field : row) {
switch (field) {
case Field::ball:
os << symbolBall;
break;
case Field::brick1Hits:
os << symbolBrick1Hit;
break;
case Field::brick2Hits:
os << symbolBrick2Hit;
break;
case Field::brick3Hits:
os << symbolBrick3Hit;
break;
case Field::brick4Hits:
os << symbolBrick4Hit;
break;
case Field::brick5Hits:
os << symbolBrick5Hit;
break;
case Field::brick6Hits:
os << symbolBrick6Hit;
break;
case Field::brick7Hits:
os << symbolBrick7Hit;
break;
case Field::brick8Hits:
os << symbolBrick8Hit;
break;
case Field::brick9Hits:
os << symbolBrick9Hit;
break;
case Field::empty:
os << symbolEmpty;
break;
case Field::plattform:
os << symbolPlattform;
break;
case Field::wall:
os << symbolWall;
break;
}
}
os << '\n';
}
return os;
}
} // namespace arkanoid
Console.h
#ifndef CONSOLE_H
#define CONSOLE_H
#include <cstddef>
namespace console {
void resize(std::size_t x, std::size_t y);
void putCursorToStartOfConsole();
void clearScreen();
bool keyWasPressed();
char getKey();
bool leftKeyHoldDown();
bool rightKeyHoldDown();
bool spaceKeyHoldDown();
bool escKeyHoldDown();
bool rKeyHoldDown();
bool isKeyDown(int key_code);
} // namespace console
#endif
Console.cpp
#include "Console.h"
#include <cstdlib>
#include <conio.h>
#include <Windows.h>
#include <WinUser.h>
namespace console {
void resize(std::size_t x, std::size_t y)
{
HWND console = GetConsoleWindow();
RECT r;
GetWindowRect(console, &r); //stores the console's current dimensions
MoveWindow(console, r.left, r.top, x, y, true);
}
void putCursorToStartOfConsole()
{
HANDLE hOut;
COORD Position;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
Position.X = 0;
Position.Y = 0;
SetConsoleCursorPosition(hOut, Position);
}
void clearScreen()
{
std::system("cls");
}
bool keyWasPressed()
{
return static_cast<bool>(_kbhit());
}
char getKey()
{
return _getch();
}
bool leftKeyHoldDown()
{
return isKeyDown(VK_LEFT);
}
bool rightKeyHoldDown()
{
return isKeyDown(VK_RIGHT);
}
bool spaceKeyHoldDown()
{
return isKeyDown(VK_SPACE);
}
bool escKeyHoldDown()
{
return isKeyDown(VK_ESCAPE);
}
bool rKeyHoldDown()
{
return isKeyDown(0x52);
}
bool isKeyDown(int key_code)
{
return GetAsyncKeyState(key_code) & -32768;
}
}
NearlyEqual.h
#ifndef NEARLYEQUAL_H
#define NEARLYEQUAL_H
namespace arkanoid{
bool nearlyEqual(double a, double b);
bool nearlyEqual(double a, double b, int factor);
}
#endif
NearlyEqual.cpp
#include "NearlyEqual.h"
#include <limits>
#include <cmath>
namespace arkanoid {
bool nearlyEqual(double a, double b)
{
return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
&& std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}
bool nearlyEqual(double a, double b, int factor)
{
double min_a = a - (a - std::nextafter(
a, std::numeric_limits<double>::lowest())) * factor;
double max_a = a + (
std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;
return min_a <= b && max_a >= b;
}
} // namespace arkanoid
MathHelper.h
#ifndef MATHHELPER_H
#define MATHHELPER_H
#define _USE_MATH_DEFINES
#include <math.h>
#include <limits>
namespace arkanoid {
constexpr double pi = M_PI;
constexpr double deg_0 = 0.0;
constexpr double deg_15 = pi / 12.0;
constexpr double deg_30 = 2 * deg_15;
constexpr double deg_45 = pi / 4.0;
constexpr double deg_60 = deg_45 + deg_15;
constexpr double deg_75 = deg_60 + deg_15;
constexpr double deg_90 = pi / 2.0;
constexpr double deg_105 = deg_90 + deg_15;
constexpr double deg_120 = deg_105 + deg_15;
constexpr double deg_135 = deg_120 + deg_15;
constexpr double deg_150 = deg_135 + deg_15;
constexpr double deg_165 = deg_150 + deg_15;
constexpr double deg_180 = pi;
constexpr double deg_195 = deg_180 + deg_15;
constexpr double deg_210 = deg_195 + deg_15;
constexpr double deg_225 = deg_210 + deg_15;
constexpr double deg_240 = deg_225 + deg_15;
constexpr double deg_255 = deg_240 + deg_15;
constexpr double deg_270 = 3 * deg_90;
constexpr double deg_285 = deg_270 + deg_15;
constexpr double deg_300 = deg_285 + deg_15;
constexpr double deg_315 = deg_270 + deg_45;
constexpr double deg_330 = deg_315 + deg_15;
constexpr double deg_345 = deg_330 + deg_15;
constexpr double deg_360 = 2 * pi;
constexpr double degreesToRadient(double degree)
{
return degree * pi / 180.0;
}
constexpr double radientToDegrees(double radient)
{
return radient * 180.0 / pi;
}
} // namespace arkanoid
#endif