|
|
@ -1,437 +1,222 @@
|
|
|
|
#include <SFML/Window.hpp>
|
|
|
|
#include <SFML/Window.hpp>
|
|
|
|
#include <SFML/Graphics.hpp>
|
|
|
|
#include <SFML/Graphics.hpp>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
#include <list>
|
|
|
|
#include <list>
|
|
|
|
#include <cmath>
|
|
|
|
#include <cmath>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include "arkanoid.hpp"
|
|
|
|
#include <ctime>
|
|
|
|
#include "ball.hpp"
|
|
|
|
|
|
|
|
#include "paddle.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
const double pi = 3.14159265358979323846;
|
|
|
|
const double pi = 3.14159265358979323846;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Arkanoid::addRandomBonus(sf::Vector2f position)
|
|
|
|
// Вспомагательные функции для работы с векторами типа sf::Vector2f
|
|
|
|
|
|
|
|
float operator*(const sf::Vector2f& first, const sf::Vector2f& second)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return first.x * second.x + first.y * second.y;
|
|
|
|
if (m_bonuses.size() > kMaxNumBonuses)
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
int max_rand = 10000;
|
|
|
|
|
|
|
|
if ((rand() % max_rand) * 1.0f / max_rand < m_bonusProbability)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_bonuses.push_back(new Bonus(position));
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float norm(sf::Vector2f a)
|
|
|
|
// Функция, которая обрабатывает все столкновения шарика
|
|
|
|
|
|
|
|
void Arkanoid::handleBallCollisions(Ball& ball)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return std::sqrt(a.x * a.x + a.y * a.y);
|
|
|
|
ball.handleWallsCollision(m_border);
|
|
|
|
|
|
|
|
ball.handlePaddleCollision(m_paddle);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto indexes = ball.handleBrickGridCollision(m_brickGrid);
|
|
|
|
|
|
|
|
if (indexes.first == -1)
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_brickGrid.deactivateBrick(indexes);
|
|
|
|
|
|
|
|
addRandomBonus(ball.position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float sqnorm(sf::Vector2f a)
|
|
|
|
|
|
|
|
|
|
|
|
Arkanoid::Arkanoid(sf::FloatRect border, sf::Font& font) :
|
|
|
|
|
|
|
|
m_time{0.0},
|
|
|
|
|
|
|
|
m_border{border},
|
|
|
|
|
|
|
|
m_paddle{{m_border.left + m_border.width / 2, m_border.top + m_border.height - 100}, {120, 20}},
|
|
|
|
|
|
|
|
m_gameState{GameState::stuck},
|
|
|
|
|
|
|
|
m_numLives{7}
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return a.x * a.x + a.y * a.y;
|
|
|
|
float gap = border.width / 10;
|
|
|
|
|
|
|
|
m_brickGrid = BrickGrid({border.left + gap, border.top + gap, border.width - 2 * gap, border.height / 2}, 50, 30);
|
|
|
|
|
|
|
|
m_bonusProbability = 0.1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
m_endText.setFont(font);
|
|
|
|
|
|
|
|
m_endText.setString("You Win!");
|
|
|
|
|
|
|
|
m_endText.setCharacterSize(100);
|
|
|
|
|
|
|
|
m_endText.setFillColor(sf::Color::White);
|
|
|
|
|
|
|
|
sf::FloatRect textRect = m_endText.getLocalBounds();
|
|
|
|
|
|
|
|
m_endText.setOrigin(textRect.left + textRect.width / 2.0f, textRect.top + textRect.height / 2.0f);
|
|
|
|
|
|
|
|
m_endText.setPosition({border.left + border.width / 2, border.top + border.height / 2});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Описываем все классы, которые мы будем использовать в программе
|
|
|
|
sf::FloatRect Arkanoid::getBorder() const
|
|
|
|
// Это нужно сделать так как даже определение одного класса может зависеть от другого
|
|
|
|
|
|
|
|
// Например, класс Bonus зависит от класса Arkanoid и наоборот
|
|
|
|
|
|
|
|
struct Ball;
|
|
|
|
|
|
|
|
struct Brick;
|
|
|
|
|
|
|
|
struct Paddle;
|
|
|
|
|
|
|
|
class Bonus;
|
|
|
|
|
|
|
|
class BrickGrid;
|
|
|
|
|
|
|
|
class Arkanoid;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "paddle.hpp"
|
|
|
|
|
|
|
|
#include "brick.hpp"
|
|
|
|
|
|
|
|
#include "ball.hpp"
|
|
|
|
|
|
|
|
#include "bonus.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Arkanoid
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
return m_border;
|
|
|
|
// Константы:
|
|
|
|
}
|
|
|
|
// Цвет задника
|
|
|
|
|
|
|
|
const sf::Color kBackgroundColor {12, 31, 47};
|
|
|
|
|
|
|
|
// Максимально возможное количество шариков в один момент времени
|
|
|
|
|
|
|
|
const unsigned kMaxNumBalls {250};
|
|
|
|
|
|
|
|
// Максимально возможное количество бонусов в один момент времени
|
|
|
|
|
|
|
|
const unsigned kMaxNumBonuses {10};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Поля:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Время, которое прошло с начала игры в секундах
|
|
|
|
|
|
|
|
double m_time;
|
|
|
|
|
|
|
|
// Границы игрового поля
|
|
|
|
|
|
|
|
sf::FloatRect m_border;
|
|
|
|
|
|
|
|
// Связный список всех шариков
|
|
|
|
|
|
|
|
std::list<Ball> m_balls;
|
|
|
|
|
|
|
|
// Объект, задающий состояние сетки блоков
|
|
|
|
|
|
|
|
BrickGrid m_brickGrid;
|
|
|
|
|
|
|
|
// Ракетка
|
|
|
|
|
|
|
|
Paddle m_paddle;
|
|
|
|
|
|
|
|
// Состояние игры
|
|
|
|
|
|
|
|
enum class GameState {stuck, running, endLose, endWin};
|
|
|
|
|
|
|
|
GameState m_gameState;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Текущее число жизней
|
|
|
|
|
|
|
|
int m_numLives;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Связный список указателей на бонусы
|
|
|
|
const Paddle& Arkanoid::getPaddle() const
|
|
|
|
// Почему указатели - для реализации полиформизма
|
|
|
|
{
|
|
|
|
// Так как в будущем мы хотим сделать несколько вариантов бонусов
|
|
|
|
return m_paddle;
|
|
|
|
std::list<Bonus*> m_bonuses;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Вероятность того, что при разрушении блока выпадет бонус
|
|
|
|
const BrickGrid& Arkanoid::getBrickGrid() const
|
|
|
|
float m_bonusProbability;
|
|
|
|
{
|
|
|
|
|
|
|
|
return m_brickGrid;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Макет шарика, используемый для рисова
|
|
|
|
void Arkanoid::addBall(const Ball& ball)
|
|
|
|
Ball m_initialBall {6, {0, 0}, {0, 0}};
|
|
|
|
{
|
|
|
|
|
|
|
|
if (m_balls.size() < kMaxNumBalls)
|
|
|
|
|
|
|
|
m_balls.push_back(ball);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Текст, который рисуется в конце игры
|
|
|
|
// Эта функция вызывается каждый кадр
|
|
|
|
sf::Text m_endText;
|
|
|
|
void Arkanoid::update(const sf::RenderWindow& window, float dt)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_time += dt;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Устанавливаем положение ракетки
|
|
|
|
|
|
|
|
sf::Vector2f mousePosition = window.mapPixelToCoords(sf::Mouse::getPosition(window));
|
|
|
|
|
|
|
|
m_paddle.position.x = mousePosition.x;
|
|
|
|
|
|
|
|
|
|
|
|
void addRandomBonus(sf::Vector2f position)
|
|
|
|
// Обрабатываем шарики
|
|
|
|
|
|
|
|
for (std::list<Ball>::iterator it = m_balls.begin(); it != m_balls.end();)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (m_bonuses.size() > kMaxNumBonuses)
|
|
|
|
(*it).update(dt);
|
|
|
|
return;
|
|
|
|
handleBallCollisions(*it);
|
|
|
|
int max_rand = 10000;
|
|
|
|
if ((*it).position.y > m_border.top + m_border.height)
|
|
|
|
if ((rand() % max_rand) * 1.0f / max_rand < m_bonusProbability)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_bonuses.push_back(new Bonus(position));
|
|
|
|
it = m_balls.erase(it);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
it++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Функция, которая обрабатывает все столкновения шарика
|
|
|
|
// Если шариков нет, то переходи в режим начала игры и уменьшаем кол-во жизней
|
|
|
|
void handleBallCollisions(Ball& ball)
|
|
|
|
if (m_gameState == GameState::running && m_balls.size() == 0)
|
|
|
|
{
|
|
|
|
|
|
|
|
ball.handleWallsCollision(m_border);
|
|
|
|
|
|
|
|
ball.handlePaddleCollision(m_paddle);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto indexes = ball.handleBrickGridCollision(m_brickGrid);
|
|
|
|
|
|
|
|
if (indexes.first == -1)
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_brickGrid.deactivateBrick(indexes);
|
|
|
|
|
|
|
|
addRandomBonus(ball.position);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
Arkanoid(sf::FloatRect border, sf::Font& font) :
|
|
|
|
|
|
|
|
m_time{0.0},
|
|
|
|
|
|
|
|
m_border{border},
|
|
|
|
|
|
|
|
m_paddle{{m_border.left + m_border.width / 2, m_border.top + m_border.height - 100}, {120, 20}},
|
|
|
|
|
|
|
|
m_gameState{GameState::stuck},
|
|
|
|
|
|
|
|
m_numLives{7}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
float gap = border.width / 10;
|
|
|
|
|
|
|
|
m_brickGrid = BrickGrid({border.left + gap, border.top + gap, border.width - 2 * gap, border.height / 2}, 50, 30);
|
|
|
|
|
|
|
|
m_bonusProbability = 0.1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
m_endText.setFont(font);
|
|
|
|
|
|
|
|
m_endText.setString("You Win!");
|
|
|
|
|
|
|
|
m_endText.setCharacterSize(100);
|
|
|
|
|
|
|
|
m_endText.setFillColor(sf::Color::White);
|
|
|
|
|
|
|
|
sf::FloatRect textRect = m_endText.getLocalBounds();
|
|
|
|
|
|
|
|
m_endText.setOrigin(textRect.left + textRect.width / 2.0f, textRect.top + textRect.height / 2.0f);
|
|
|
|
|
|
|
|
m_endText.setPosition({border.left + border.width / 2, border.top + border.height / 2});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sf::FloatRect getBorder() const
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return m_border;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Paddle& getPaddle() const
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return m_paddle;
|
|
|
|
m_gameState = GameState::stuck;
|
|
|
|
|
|
|
|
m_numLives--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const BrickGrid& getBrickGrid() const
|
|
|
|
// Если жизни кончились, то переходим в состояние конца игры (проигрыш)
|
|
|
|
|
|
|
|
if (m_numLives < 0)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return m_brickGrid;
|
|
|
|
m_gameState = GameState::endLose;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void addBall(const Ball& ball)
|
|
|
|
// Если блоки кончились, то переходим в состояние конца игры (победа)
|
|
|
|
|
|
|
|
if (m_brickGrid.getNumActiveBricks() == 0)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (m_balls.size() < kMaxNumBalls)
|
|
|
|
m_gameState = GameState::endWin;
|
|
|
|
m_balls.push_back(ball);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Эта функция вызывается каждый кадр
|
|
|
|
// Обрабатываем бонусы
|
|
|
|
void update(const sf::RenderWindow& window, float dt)
|
|
|
|
for (auto it = m_bonuses.begin(); it != m_bonuses.end();)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_time += dt;
|
|
|
|
(*it)->update(dt);
|
|
|
|
|
|
|
|
if ((*it)->isColiding(m_paddle))
|
|
|
|
// Устанавливаем положение ракетки
|
|
|
|
|
|
|
|
sf::Vector2f mousePosition = window.mapPixelToCoords(sf::Mouse::getPosition(window));
|
|
|
|
|
|
|
|
m_paddle.position.x = mousePosition.x;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Обрабатываем шарики
|
|
|
|
|
|
|
|
for (std::list<Ball>::iterator it = m_balls.begin(); it != m_balls.end();)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
(*it).update(dt);
|
|
|
|
(*it)->activate(*this);
|
|
|
|
handleBallCollisions(*it);
|
|
|
|
delete *it;
|
|
|
|
if ((*it).position.y > m_border.top + m_border.height)
|
|
|
|
it = m_bonuses.erase(it);
|
|
|
|
{
|
|
|
|
|
|
|
|
it = m_balls.erase(it);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
it++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else if ((*it)->m_position.y > m_border.top + m_border.height)
|
|
|
|
// Если шариков нет, то переходи в режим начала игры и уменьшаем кол-во жизней
|
|
|
|
{
|
|
|
|
if (m_gameState == GameState::running && m_balls.size() == 0)
|
|
|
|
delete (*it);
|
|
|
|
{
|
|
|
|
it = m_bonuses.erase(it);
|
|
|
|
m_gameState = GameState::stuck;
|
|
|
|
|
|
|
|
m_numLives--;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
// Если жизни кончились, то переходим в состояние конца игры (проигрыш)
|
|
|
|
|
|
|
|
if (m_numLives < 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_gameState = GameState::endLose;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Если блоки кончились, то переходим в состояние конца игры (победа)
|
|
|
|
|
|
|
|
if (m_brickGrid.getNumActiveBricks() == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_gameState = GameState::endWin;
|
|
|
|
it++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Обрабатываем бонусы
|
|
|
|
|
|
|
|
for (auto it = m_bonuses.begin(); it != m_bonuses.end();)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
(*it)->update(dt);
|
|
|
|
|
|
|
|
if ((*it)->isColiding(m_paddle))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
(*it)->activate(*this);
|
|
|
|
|
|
|
|
delete *it;
|
|
|
|
|
|
|
|
it = m_bonuses.erase(it);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else if ((*it)->m_position.y > m_border.top + m_border.height)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
delete (*it);
|
|
|
|
|
|
|
|
it = m_bonuses.erase(it);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
it++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void draw(sf::RenderWindow& window)
|
|
|
|
void Arkanoid::draw(sf::RenderWindow& window)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// Рисуем задний прямоугольник
|
|
|
|
// Рисуем задний прямоугольник
|
|
|
|
static sf::RectangleShape background {{m_border.width, m_border.height}};
|
|
|
|
static sf::RectangleShape background {{m_border.width, m_border.height}};
|
|
|
|
background.setPosition({m_border.left, m_border.top});
|
|
|
|
background.setPosition({m_border.left, m_border.top});
|
|
|
|
background.setFillColor(kBackgroundColor);
|
|
|
|
background.setFillColor(kBackgroundColor);
|
|
|
|
window.draw(background);
|
|
|
|
window.draw(background);
|
|
|
|
|
|
|
|
|
|
|
|
// Рисуем блоки
|
|
|
|
|
|
|
|
m_brickGrid.draw(window);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Рисуем шарики
|
|
|
|
|
|
|
|
for (Ball& ball : m_balls)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
ball.draw(window);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Рисуем ракетку
|
|
|
|
|
|
|
|
m_paddle.draw(window);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Если мы в режиме начала игры, то рисуем шарик на ракетке
|
|
|
|
|
|
|
|
if (m_gameState == GameState::stuck)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_initialBall.position = {m_paddle.position.x, m_paddle.position.y - m_paddle.size.y / 2 - m_initialBall.radius};
|
|
|
|
|
|
|
|
m_initialBall.position = {m_paddle.position.x, m_paddle.position.y - m_paddle.size.y / 2 - m_initialBall.radius};
|
|
|
|
|
|
|
|
m_initialBall.draw(window);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Рисуем кол-во жизней вверху слева
|
|
|
|
// Рисуем блоки
|
|
|
|
for (int i = 0; i < m_numLives; i++)
|
|
|
|
m_brickGrid.draw(window);
|
|
|
|
{
|
|
|
|
|
|
|
|
m_initialBall.position = {m_initialBall.radius * (3 * i + 2), 2 * m_initialBall.radius};
|
|
|
|
|
|
|
|
m_initialBall.draw(window);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Рисуем бонусы
|
|
|
|
// Рисуем шарики
|
|
|
|
for (Bonus* pbonus : m_bonuses)
|
|
|
|
for (Ball& ball : m_balls)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
pbonus->draw(window);
|
|
|
|
ball.draw(window);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// При завершении игры рисуем надпись
|
|
|
|
// Рисуем ракетку
|
|
|
|
if (m_gameState == GameState::endWin)
|
|
|
|
m_paddle.draw(window);
|
|
|
|
{
|
|
|
|
|
|
|
|
m_endText.setString("You Win!");
|
|
|
|
|
|
|
|
window.draw(m_endText);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// При завершении игры рисуем надпись
|
|
|
|
// Если мы в режиме начала игры, то рисуем шарик на ракетке
|
|
|
|
if (m_gameState == GameState::endLose)
|
|
|
|
if (m_gameState == GameState::stuck)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_endText.setString("You Lose!");
|
|
|
|
m_initialBall.position = {m_paddle.position.x, m_paddle.position.y - m_paddle.size.y / 2 - m_initialBall.radius};
|
|
|
|
window.draw(m_endText);
|
|
|
|
m_initialBall.position = {m_paddle.position.x, m_paddle.position.y - m_paddle.size.y / 2 - m_initialBall.radius};
|
|
|
|
}
|
|
|
|
m_initialBall.draw(window);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void onMousePressed(sf::Event& event)
|
|
|
|
// Рисуем кол-во жизней вверху слева
|
|
|
|
|
|
|
|
for (int i = 0; i < m_numLives; i++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
switch (m_gameState)
|
|
|
|
m_initialBall.position = {m_initialBall.radius * (3 * i + 2), 2 * m_initialBall.radius};
|
|
|
|
{
|
|
|
|
m_initialBall.draw(window);
|
|
|
|
case GameState::stuck:
|
|
|
|
|
|
|
|
if (event.mouseButton.button == sf::Mouse::Left)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_gameState = GameState::running;
|
|
|
|
|
|
|
|
float velocityAngle = (rand() % 100 + 40) * pi / 180;
|
|
|
|
|
|
|
|
float velocityNorm = Ball::initialVelocity;
|
|
|
|
|
|
|
|
sf::Vector2f newPosition = {m_paddle.position.x, m_paddle.position.y - m_paddle.size.y / 2.0f - m_initialBall.radius};
|
|
|
|
|
|
|
|
sf::Vector2f newVelocity = {-velocityNorm * cosf(velocityAngle), -velocityNorm * sinf(velocityAngle)};
|
|
|
|
|
|
|
|
addBall({m_initialBall.radius, newPosition, newVelocity});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case GameState::running:
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GameState::endLose:
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GameState::endWin:
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Класс бонус должен быть дружественным, так как он может менять внутреннее состояние игры
|
|
|
|
// Рисуем бонусы
|
|
|
|
friend class Bonus;
|
|
|
|
for (Bonus* pbonus : m_bonuses)
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Bonus::Bonus(sf::Vector2f position): m_position(position)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_time = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Двигаем бонус
|
|
|
|
|
|
|
|
void Bonus::update(float dt)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_time += dt;
|
|
|
|
|
|
|
|
m_position.y += speed * dt;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Рисуем бонус
|
|
|
|
|
|
|
|
void Bonus::draw(sf::RenderWindow& window) const
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// Рисуем белый круг
|
|
|
|
|
|
|
|
static sf::CircleShape shape(radius);
|
|
|
|
|
|
|
|
shape.setOrigin(radius, radius);
|
|
|
|
|
|
|
|
shape.setFillColor(sf::Color{100, 200, 100});
|
|
|
|
|
|
|
|
shape.setPosition(m_position);
|
|
|
|
|
|
|
|
window.draw(shape);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float angle = 0;
|
|
|
|
|
|
|
|
// Рисуем 3 шарика на этом круге
|
|
|
|
|
|
|
|
static Ball ball {5, {0, 0}, {0, 0}};
|
|
|
|
|
|
|
|
float ballRotationRadius = 7;
|
|
|
|
|
|
|
|
ball.position = m_position + ballRotationRadius * sf::Vector2f(std::cos(angle), std::sin(angle));
|
|
|
|
|
|
|
|
ball.draw(window);
|
|
|
|
|
|
|
|
angle += 2.0 * pi / 3.0;
|
|
|
|
|
|
|
|
ball.position = m_position + ballRotationRadius * sf::Vector2f(std::cos(angle), std::sin(angle));
|
|
|
|
|
|
|
|
ball.draw(window);
|
|
|
|
|
|
|
|
angle += 2.0 * pi / 3.0;
|
|
|
|
|
|
|
|
ball.position = m_position + ballRotationRadius * sf::Vector2f(std::cos(angle), std::sin(angle));
|
|
|
|
|
|
|
|
ball.draw(window);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Функция Bonus::activate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Применяем эффект бонуса (в данном случае - утроение шариков)
|
|
|
|
|
|
|
|
numBalls - Количество шариков до утроения
|
|
|
|
|
|
|
|
Шарики хранятся в связном списке m_balls
|
|
|
|
|
|
|
|
Так как мы работаем со связным списком, то придётся использовать итератор it
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Проходим итератором по изначальным элементам списка и добавляем новые шарики в список
|
|
|
|
|
|
|
|
В данном случае простой цикл через итераторы не сработает, так как массив game.m_balls увеличивается в процессе выполнения цикла.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Внутри цикла выбираем случайный вектор скорости и добавляем шарик в список game.m_balls
|
|
|
|
|
|
|
|
Делаем то же самое для ещё одного шарика
|
|
|
|
|
|
|
|
В конце цикла переходим ко следующему шарику в списке, т.е. увеличивем итератор it
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Bonus::activate(Arkanoid& game)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
int numBalls = game.m_balls.size();
|
|
|
|
|
|
|
|
std::list<Ball>::iterator it = game.m_balls.begin();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < numBalls; i++)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
float angle = rand() % 1000 * (2 * pi / 1000);
|
|
|
|
pbonus->draw(window);
|
|
|
|
float vx = Ball::initialVelocity * sin(angle);
|
|
|
|
|
|
|
|
float vy = Ball::initialVelocity * cos(angle);
|
|
|
|
|
|
|
|
game.addBall({game.m_initialBall.radius, (*it).position, {vx, vy}});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
angle = rand() % 1000 * (2 * pi / 1000);
|
|
|
|
|
|
|
|
vx = Ball::initialVelocity * sin(angle);
|
|
|
|
|
|
|
|
vy = Ball::initialVelocity * cos(angle);
|
|
|
|
|
|
|
|
game.addBall({game.m_initialBall.radius, (*it).position, {vx, vy}});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it++;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Bonus::isColiding(const Paddle& paddle) const
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
bool result = paddle.getBorder().intersects({m_position.x - radius, m_position.y - radius, 2 * radius, 2 * radius});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// При завершении игры рисуем надпись
|
|
|
|
int main ()
|
|
|
|
if (m_gameState == GameState::endWin)
|
|
|
|
{
|
|
|
|
|
|
|
|
srand(time(0));
|
|
|
|
|
|
|
|
sf::ContextSettings settings;
|
|
|
|
|
|
|
|
settings.antialiasingLevel = 8;
|
|
|
|
|
|
|
|
sf::RenderWindow window(sf::VideoMode(1000, 800, 32), "Arkanoid", sf::Style::Default, settings);
|
|
|
|
|
|
|
|
window.setFramerateLimit(120);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sf::Clock clock;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sf::Font font;
|
|
|
|
|
|
|
|
if (!font.loadFromFile("consola.ttf"))
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::cout << "Can't load font consola.ttf" << std::endl;
|
|
|
|
m_endText.setString("You Win!");
|
|
|
|
std::exit(1);
|
|
|
|
window.draw(m_endText);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Arkanoid game({0, 0, 1000, 800}, font);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (window.isOpen())
|
|
|
|
// При завершении игры рисуем надпись
|
|
|
|
|
|
|
|
if (m_gameState == GameState::endLose)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
float dt = clock.restart().asSeconds();
|
|
|
|
m_endText.setString("You Lose!");
|
|
|
|
std::cout << "FPS=" << static_cast<int>(1.0 / dt) << "\n";
|
|
|
|
window.draw(m_endText);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Обработка событий
|
|
|
|
void Arkanoid::onMousePressed(sf::Event& event)
|
|
|
|
sf::Event event;
|
|
|
|
{
|
|
|
|
while(window.pollEvent(event))
|
|
|
|
switch (m_gameState)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if(event.type == sf::Event::Closed || (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape))
|
|
|
|
case GameState::stuck:
|
|
|
|
{
|
|
|
|
if (event.mouseButton.button == sf::Mouse::Left)
|
|
|
|
window.close();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.type == sf::Event::MouseButtonPressed)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
game.onMousePressed(event);
|
|
|
|
m_gameState = GameState::running;
|
|
|
|
|
|
|
|
float velocityAngle = (rand() % 100 + 40) * pi / 180;
|
|
|
|
|
|
|
|
float velocityNorm = Ball::initialVelocity;
|
|
|
|
|
|
|
|
sf::Vector2f newPosition = {m_paddle.position.x, m_paddle.position.y - m_paddle.size.y / 2.0f - m_initialBall.radius};
|
|
|
|
|
|
|
|
sf::Vector2f newVelocity = {-velocityNorm * cosf(velocityAngle), -velocityNorm * sinf(velocityAngle)};
|
|
|
|
|
|
|
|
addBall({m_initialBall.radius, newPosition, newVelocity});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
window.clear(sf::Color(0, 0, 0));
|
|
|
|
|
|
|
|
// Расчитываем новые координаты и новую скорость шарика
|
|
|
|
case GameState::running:
|
|
|
|
game.update(window, dt);
|
|
|
|
break;
|
|
|
|
game.draw(window);
|
|
|
|
case GameState::endLose:
|
|
|
|
|
|
|
|
break;
|
|
|
|
// Отображам всё нарисованное на временном "холсте" на экран
|
|
|
|
case GameState::endWin:
|
|
|
|
window.display();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|