added double jump and sitting state
This commit is contained in:
parent
2e5c5a8dde
commit
90d07dde3f
148 changed files with 13050 additions and 0 deletions
3
term1/seminar13_polymorphism/arkanoid/Makefile
Normal file
3
term1/seminar13_polymorphism/arkanoid/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
path = ../../../../3rdparty/SFML-2.5.1
|
||||
arkanoid:
|
||||
g++ -Wall -Wextra arkanoid.cpp bonus.cpp main.cpp ball.cpp brick_grid.cpp paddle.cpp -std=c++17 -o arkanoid -lsfml-graphics -lsfml-window -lsfml-system
|
||||
BIN
term1/seminar13_polymorphism/arkanoid/arkanoid
Executable file
BIN
term1/seminar13_polymorphism/arkanoid/arkanoid
Executable file
Binary file not shown.
258
term1/seminar13_polymorphism/arkanoid/arkanoid.cpp
Normal file
258
term1/seminar13_polymorphism/arkanoid/arkanoid.cpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
#include <SFML/Window.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <list>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
#include "arkanoid.hpp"
|
||||
#include "bonus.hpp"
|
||||
|
||||
const double pi = 3.14159265358979323846;
|
||||
|
||||
void Arkanoid::addRandomBonus(sf::Vector2f position)
|
||||
{
|
||||
if (m_bonuses.size() > kMaxNumBonuses)
|
||||
return;
|
||||
int max_rand = 10000;
|
||||
if ((rand() % max_rand) * 1.0f / max_rand < m_bonusProbability)
|
||||
{
|
||||
int max = 4; int min = 1;
|
||||
int range = max - min + 1;
|
||||
int num = rand() % range + min;
|
||||
switch (num) {
|
||||
case 1:
|
||||
m_bonuses.push_back(new TripleBallBonus(position));
|
||||
break;
|
||||
case 2:
|
||||
m_bonuses.push_back(new EnlargePaddleBonus(position));
|
||||
break;
|
||||
case 3:
|
||||
m_bonuses.push_back(new ShrinkPaddleBonus(position));
|
||||
break;
|
||||
case 4:
|
||||
m_bonuses.push_back(new SlowingBonus(position));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Функция, которая обрабатывает все столкновения шарика
|
||||
void Arkanoid::handleBallCollisions(Ball& ball)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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}
|
||||
{
|
||||
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
|
||||
{
|
||||
return m_border;
|
||||
}
|
||||
|
||||
const Paddle& Arkanoid::getPaddle() const
|
||||
{
|
||||
return m_paddle;
|
||||
}
|
||||
|
||||
const BrickGrid& Arkanoid::getBrickGrid() const
|
||||
{
|
||||
return m_brickGrid;
|
||||
}
|
||||
|
||||
void Arkanoid::addBall(const Ball& ball)
|
||||
{
|
||||
if (m_balls.size() < kMaxNumBalls)
|
||||
m_balls.push_back(ball);
|
||||
}
|
||||
|
||||
bool Arkanoid::isMaxBalls()
|
||||
{
|
||||
return m_balls.size() == kMaxNumBalls - 1;
|
||||
}
|
||||
|
||||
// Эта функция вызывается каждый кадр
|
||||
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;
|
||||
|
||||
// Обрабатываем шарики
|
||||
for (std::list<Ball>::iterator it = m_balls.begin(); it != m_balls.end();)
|
||||
{
|
||||
(*it).update(dt);
|
||||
handleBallCollisions(*it);
|
||||
if ((*it).position.y > m_border.top + m_border.height)
|
||||
{
|
||||
it = m_balls.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
// Если шариков нет, то переходи в режим начала игры и уменьшаем кол-во жизней
|
||||
if (m_gameState == GameState::running && m_balls.size() == 0)
|
||||
{
|
||||
m_effects.clear();
|
||||
m_gameState = GameState::stuck;
|
||||
m_numLives--;
|
||||
}
|
||||
|
||||
// Если жизни кончились, то переходим в состояние конца игры (проигрыш)
|
||||
if (m_numLives < 0)
|
||||
{
|
||||
m_gameState = GameState::endLose;
|
||||
}
|
||||
|
||||
// Если блоки кончились, то переходим в состояние конца игры (победа)
|
||||
if (m_brickGrid.getNumActiveBricks() == 0)
|
||||
{
|
||||
m_gameState = GameState::endWin;
|
||||
}
|
||||
|
||||
// Обрабатываем бонусы
|
||||
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++;
|
||||
}
|
||||
|
||||
}
|
||||
/* Обработка эффектов */
|
||||
for (auto it = m_effects.begin(); it != m_effects.end();)
|
||||
{
|
||||
if ((*it)->isExpired(m_time))
|
||||
{
|
||||
(*it)->deactivate(*this);
|
||||
delete *it;
|
||||
it = m_effects.erase(it);
|
||||
}
|
||||
else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Arkanoid::draw(sf::RenderWindow& window)
|
||||
{
|
||||
// Рисуем задний прямоугольник
|
||||
static sf::RectangleShape background {{m_border.width, m_border.height}};
|
||||
background.setPosition({m_border.left, m_border.top});
|
||||
background.setFillColor(kBackgroundColor);
|
||||
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_initialBall.position = {m_initialBall.radius * (3 * i + 2), 2 * m_initialBall.radius};
|
||||
m_initialBall.draw(window);
|
||||
}
|
||||
|
||||
// Рисуем бонусы
|
||||
for (Bonus* pbonus : m_bonuses)
|
||||
{
|
||||
pbonus->draw(window);
|
||||
}
|
||||
|
||||
// При завершении игры рисуем надпись
|
||||
if (m_gameState == GameState::endWin)
|
||||
{
|
||||
m_endText.setString("You Win!");
|
||||
window.draw(m_endText);
|
||||
}
|
||||
|
||||
// При завершении игры рисуем надпись
|
||||
if (m_gameState == GameState::endLose)
|
||||
{
|
||||
m_endText.setString("You Lose!");
|
||||
window.draw(m_endText);
|
||||
}
|
||||
}
|
||||
|
||||
void Arkanoid::onMousePressed(sf::Event& event)
|
||||
{
|
||||
switch (m_gameState)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
85
term1/seminar13_polymorphism/arkanoid/arkanoid.hpp
Normal file
85
term1/seminar13_polymorphism/arkanoid/arkanoid.hpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#pragma once
|
||||
#include <list>
|
||||
|
||||
#include "ball.hpp"
|
||||
#include "brick_grid.hpp"
|
||||
#include "paddle.hpp"
|
||||
class Bonus;
|
||||
class Effect;
|
||||
|
||||
class Arkanoid
|
||||
{
|
||||
private:
|
||||
// Константы:
|
||||
// Цвет задника
|
||||
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;
|
||||
|
||||
// Связный список указателей на бонусы
|
||||
// Почему указатели - для реализации полиформизма
|
||||
// Так как в будущем мы хотим сделать несколько вариантов бонусов
|
||||
std::list<Bonus*> m_bonuses;
|
||||
std::list<Effect*> m_effects;
|
||||
|
||||
// Вероятность того, что при разрушении блока выпадет бонус
|
||||
float m_bonusProbability;
|
||||
|
||||
// Макет шарика, используемый для рисова
|
||||
Ball m_initialBall {6, {0, 0}, {0, 0}};
|
||||
|
||||
// Текст, который рисуется в конце игры
|
||||
sf::Text m_endText;
|
||||
|
||||
|
||||
void addRandomBonus(sf::Vector2f position);
|
||||
|
||||
// Функция, которая обрабатывает все столкновения шарика
|
||||
void handleBallCollisions(Ball& ball);
|
||||
|
||||
public:
|
||||
Arkanoid(sf::FloatRect border, sf::Font& font);
|
||||
|
||||
sf::FloatRect getBorder() const;
|
||||
|
||||
const Paddle& getPaddle() const;
|
||||
|
||||
const BrickGrid& getBrickGrid() const;
|
||||
|
||||
void addBall(const Ball& ball);
|
||||
|
||||
// Эта функция вызывается каждый кадр
|
||||
void update(const sf::RenderWindow& window, float dt);
|
||||
|
||||
void draw(sf::RenderWindow& window);
|
||||
void onMousePressed(sf::Event& event);
|
||||
bool isMaxBalls();
|
||||
|
||||
// Класс бонус должен быть дружественным, так как он может менять внутреннее состояние игры
|
||||
friend class Bonus;
|
||||
friend class TripleBallBonus;
|
||||
friend class EnlargePaddleBonus;
|
||||
friend class ShrinkPaddleBonus;
|
||||
friend class SlowingBonus;
|
||||
|
||||
friend class SlowingEffect;
|
||||
};
|
||||
183
term1/seminar13_polymorphism/arkanoid/ball.cpp
Normal file
183
term1/seminar13_polymorphism/arkanoid/ball.cpp
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
#include <cmath>
|
||||
#include "ball.hpp"
|
||||
#include "brick_grid.hpp"
|
||||
#include "paddle.hpp"
|
||||
|
||||
// Вспомагательные функции для работы с векторами типа sf::Vector2f
|
||||
float operator*(const sf::Vector2f& first, const sf::Vector2f& second)
|
||||
{
|
||||
return first.x * second.x + first.y * second.y;
|
||||
}
|
||||
|
||||
float norm(sf::Vector2f a)
|
||||
{
|
||||
return std::sqrt(a.x * a.x + a.y * a.y);
|
||||
}
|
||||
|
||||
float sqnorm(sf::Vector2f a)
|
||||
{
|
||||
return a.x * a.x + a.y * a.y;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Ball::Ball(float radius, sf::Vector2f position, sf::Vector2f velocity) :
|
||||
radius(radius), position(position), velocity(velocity) {}
|
||||
|
||||
void Ball::update(float dt)
|
||||
{
|
||||
position += velocity * dt;
|
||||
}
|
||||
|
||||
void Ball::draw(sf::RenderWindow& window)
|
||||
{
|
||||
static sf::CircleShape shape {};
|
||||
shape.setRadius(radius);
|
||||
shape.setOrigin(radius, radius);
|
||||
shape.setFillColor(color);
|
||||
shape.setPosition(position);
|
||||
window.draw(shape);
|
||||
}
|
||||
|
||||
std::pair<sf::Vector2f, bool> Ball::findClosestPoint(const sf::FloatRect& rect) const
|
||||
{
|
||||
float left = rect.left;
|
||||
float right = rect.left + rect.width;
|
||||
float bottom = rect.top + rect.height;
|
||||
float top = rect.top;
|
||||
|
||||
sf::Vector2f d;
|
||||
if (position.x < left)
|
||||
d.x = left;
|
||||
else if (position.x > right)
|
||||
d.x = right;
|
||||
else
|
||||
d.x = position.x;
|
||||
|
||||
if (position.y > bottom)
|
||||
d.y = bottom;
|
||||
else if (position.y < top)
|
||||
d.y = top;
|
||||
else
|
||||
d.y = position.y;
|
||||
|
||||
d -= position;
|
||||
bool isColiding = sqnorm(d) < radius * radius;
|
||||
return {d, isColiding};
|
||||
}
|
||||
|
||||
bool Ball::handleRectCollision(const sf::FloatRect& rect)
|
||||
{
|
||||
auto [d, isColiding] = findClosestPoint(rect);
|
||||
if (!isColiding)
|
||||
return false;
|
||||
|
||||
float closestPointNorm = norm(d);
|
||||
// Если расстояние == 0, то это значит, что шарик за 1 фрейм зашёл центром внутрь блока
|
||||
// Отражаем шарик от блока
|
||||
if (closestPointNorm < 1e-4)
|
||||
{
|
||||
if (std::fabs(velocity.x) > std::fabs(velocity.y))
|
||||
velocity.x *= -1;
|
||||
else
|
||||
velocity.y *= -1;
|
||||
}
|
||||
// Если расстояние != 0, но шарик касается блока, то мы можем просчитать отражение более точно
|
||||
// Отражение от углов и по касательной.
|
||||
else if (closestPointNorm < radius)
|
||||
{
|
||||
position -= d * ((radius - closestPointNorm) / closestPointNorm);
|
||||
velocity -= 2.0f * d * (d * velocity) / (closestPointNorm * closestPointNorm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Ball::handleWallsCollision(sf::FloatRect boundary)
|
||||
{
|
||||
if (position.x < boundary.left + radius)
|
||||
{
|
||||
position.x = boundary.left + radius;
|
||||
velocity.x *= -1;
|
||||
}
|
||||
if (position.x > boundary.left + boundary.width - radius)
|
||||
{
|
||||
position.x = boundary.left + boundary.width - radius;
|
||||
velocity.x *= -1;
|
||||
}
|
||||
if (position.y < boundary.top + radius)
|
||||
{
|
||||
position.y = boundary.top + radius;
|
||||
velocity.y *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::pair<int, int> Ball::handleBrickGridCollision(const BrickGrid& brickGrid)
|
||||
{
|
||||
auto [gridColumns, gridRows] = brickGrid.getGridSizes();
|
||||
auto [brickWidth, brickHeight] = brickGrid.getBrickSizes();
|
||||
auto [left, top, width, height] = brickGrid.getBorder();
|
||||
|
||||
// Определяем координаты блоков с которыми шарик может пересечься
|
||||
int brickColumnStart = std::clamp(static_cast<int>((position.x - left - radius) / brickWidth), 0, gridColumns);
|
||||
int brickColumnFinish = std::clamp(static_cast<int>((position.x - left + radius) / brickWidth) + 1, 0, gridColumns);
|
||||
int brickRowStart = std::clamp(static_cast<int>((position.y - top - radius) / brickHeight), 0, gridRows);
|
||||
int brickRowFinish = std::clamp(static_cast<int>((position.y - top + radius) / brickHeight) + 1, 0, gridRows);
|
||||
|
||||
// Если шарик находится вне сетки блоков, то выходим
|
||||
if (brickColumnStart == brickColumnFinish || brickRowStart == brickRowFinish)
|
||||
return {-1, -1};
|
||||
|
||||
// Находим ближайший к центру шарика активный пересекаемый шариком блок
|
||||
float closestSqNorm = width * width + height * height;
|
||||
std::pair<int, int> closestBrickIndexes = {-1, -1};
|
||||
for (int i = brickColumnStart; i < brickColumnFinish; ++i)
|
||||
{
|
||||
for (int j = brickRowStart; j < brickRowFinish; ++j)
|
||||
{
|
||||
if (!brickGrid.isBrickActive({i, j}))
|
||||
continue;
|
||||
|
||||
sf::FloatRect rect {left + i * brickWidth, top + j * brickHeight, brickWidth, brickHeight};
|
||||
auto [d, isColiding] = findClosestPoint(rect);
|
||||
if (!isColiding)
|
||||
continue;
|
||||
|
||||
if (sqnorm(d) < closestSqNorm)
|
||||
{
|
||||
closestSqNorm = sqnorm(d);
|
||||
closestBrickIndexes = {i, j};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если такого не нашли, то возвращаем {-1, -1}
|
||||
if (closestBrickIndexes.first == -1)
|
||||
return closestBrickIndexes;
|
||||
|
||||
// Упруго сталкиваем шарик с найденым блоком
|
||||
sf::FloatRect rect {left + closestBrickIndexes.first * brickWidth, top + closestBrickIndexes.second * brickHeight, brickWidth, brickHeight};
|
||||
handleRectCollision(rect);
|
||||
|
||||
// Возвращаем координаты блока в сетки блоков
|
||||
return closestBrickIndexes;
|
||||
}
|
||||
|
||||
// Обрабатываем столкновения шарика с ракеткой
|
||||
void Ball::handlePaddleCollision(const Paddle& paddle)
|
||||
{
|
||||
auto [d, isColiding] = findClosestPoint(paddle.getBorder());
|
||||
if (!isColiding)
|
||||
return;
|
||||
|
||||
// Столкновение не упругое
|
||||
// Угол отражения зависит от места на ракетке, куда стукнулся шарик
|
||||
// Если шарик стукнулся в левую часть ракетки, то он должен полететь влево.
|
||||
// Если в правую часть ракетки, то вправо.
|
||||
const float pi = 3.14159265;
|
||||
float velocityAngle = (position.x - paddle.position.x) / (paddle.size.x + 2 * radius) * (0.8 * pi) + pi / 2;
|
||||
float velocityNorm = norm(velocity);
|
||||
velocity.x = - velocityNorm * std::cos(velocityAngle);
|
||||
velocity.y = - velocityNorm * std::sin(velocityAngle);
|
||||
}
|
||||
31
term1/seminar13_polymorphism/arkanoid/ball.hpp
Normal file
31
term1/seminar13_polymorphism/arkanoid/ball.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
#include <SFML/Window.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
class BrickGrid;
|
||||
class Paddle;
|
||||
|
||||
struct Ball
|
||||
{
|
||||
inline static const float initialVelocity = 700;
|
||||
inline static const sf::Color color {246, 213, 92};
|
||||
float radius;
|
||||
sf::Vector2f position;
|
||||
sf::Vector2f velocity;
|
||||
|
||||
Ball(float radius, sf::Vector2f position, sf::Vector2f velocity);
|
||||
|
||||
void update(float dt);
|
||||
|
||||
void draw(sf::RenderWindow& window);
|
||||
|
||||
std::pair<sf::Vector2f, bool> findClosestPoint(const sf::FloatRect& rect) const;
|
||||
|
||||
bool handleRectCollision(const sf::FloatRect& rect);
|
||||
|
||||
void handleWallsCollision(sf::FloatRect boundary);
|
||||
|
||||
std::pair<int, int> handleBrickGridCollision(const BrickGrid& brickGrid);
|
||||
|
||||
void handlePaddleCollision(const Paddle& paddle);
|
||||
};
|
||||
221
term1/seminar13_polymorphism/arkanoid/bonus.cpp
Normal file
221
term1/seminar13_polymorphism/arkanoid/bonus.cpp
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
#include "bonus.hpp"
|
||||
#include "arkanoid.hpp"
|
||||
#include "ball.hpp"
|
||||
#include "paddle.hpp"
|
||||
|
||||
/* =============
|
||||
* ===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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* TripleBallBonus
|
||||
* */
|
||||
void TripleBallBonus::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;
|
||||
|
||||
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 * M_PI / 3.0;
|
||||
ball.position = m_position + ballRotationRadius * sf::Vector2f(std::cos(angle), std::sin(angle));
|
||||
ball.draw(window);
|
||||
angle += 2.0 * M_PI / 3.0;
|
||||
ball.position = m_position + ballRotationRadius * sf::Vector2f(std::cos(angle), std::sin(angle));
|
||||
ball.draw(window);
|
||||
}
|
||||
|
||||
void TripleBallBonus::activate(Arkanoid& game)
|
||||
{
|
||||
int numBalls = game.m_balls.size();
|
||||
std::list<Ball>::iterator it = game.m_balls.begin();
|
||||
|
||||
bool isSlowed = false;
|
||||
bool toApply = true;
|
||||
|
||||
Effect* slowing_effect = nullptr;
|
||||
for (auto it = game.m_effects.begin(); it != game.m_effects.end();) {
|
||||
if ((*it)->effectId == _EFFECT_SLOWING_ID) {
|
||||
isSlowed = true;
|
||||
slowing_effect = *it;
|
||||
break;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
for (int i = 0; i < numBalls; i++)
|
||||
{
|
||||
float angle, vx, vy;
|
||||
if (game.isMaxBalls()) {
|
||||
toApply = false;
|
||||
}
|
||||
|
||||
if (toApply) {
|
||||
angle = rand() % 1000 * (2 * M_PI / 1000);
|
||||
vx = Ball::initialVelocity * sin(angle);
|
||||
vy = Ball::initialVelocity * cos(angle);
|
||||
game.addBall({game.m_initialBall.radius, (*it).position, {vx, vy}});
|
||||
|
||||
if (isSlowed) {
|
||||
slowing_effect->activate(game.m_balls.back());
|
||||
}
|
||||
}
|
||||
|
||||
if (game.isMaxBalls()) {
|
||||
toApply = false;
|
||||
}
|
||||
|
||||
if (toApply) {
|
||||
angle = rand() % 1000 * (2 * M_PI / 1000);
|
||||
vx = Ball::initialVelocity * sin(angle);
|
||||
vy = Ball::initialVelocity * cos(angle);
|
||||
game.addBall({game.m_initialBall.radius, (*it).position, {vx, vy}});
|
||||
if (isSlowed) {
|
||||
slowing_effect->activate(game.m_balls.back());
|
||||
}
|
||||
}
|
||||
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* EnlargePaddleBonus
|
||||
* */
|
||||
void EnlargePaddleBonus::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);
|
||||
|
||||
static sf::RectangleShape rect(sf::Vector2f{radius, radius / 2});
|
||||
rect.setFillColor(sf::Color::Green);
|
||||
rect.setPosition(m_position - sf::Vector2f{radius /2, radius / 4});
|
||||
window.draw(rect);
|
||||
}
|
||||
|
||||
void EnlargePaddleBonus::activate(Arkanoid& game)
|
||||
{
|
||||
if (game.m_paddle.size.x < 300) {
|
||||
game.m_paddle.size.x *= 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ShrinkPaddleBonus
|
||||
* */
|
||||
void ShrinkPaddleBonus::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);
|
||||
|
||||
static sf::RectangleShape rect(sf::Vector2f{radius, radius / 2});
|
||||
rect.setFillColor(sf::Color::Red);
|
||||
rect.setPosition(m_position - sf::Vector2f{radius /2, radius / 4});
|
||||
window.draw(rect);
|
||||
}
|
||||
void ShrinkPaddleBonus::activate(Arkanoid& game)
|
||||
{
|
||||
if (game.m_paddle.size.x > 40) {
|
||||
game.m_paddle.size.x *= 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* SlowingBonus
|
||||
* */
|
||||
|
||||
void SlowingBonus::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);
|
||||
|
||||
static sf::CircleShape clock(radius / 2);
|
||||
clock.setOutlineColor(sf::Color::Red);
|
||||
clock.setOutlineThickness(3);
|
||||
clock.setPosition(m_position - sf::Vector2f{radius /2, radius / 2});
|
||||
window.draw(clock);
|
||||
}
|
||||
|
||||
void SlowingBonus::activate(Arkanoid& game)
|
||||
{
|
||||
bool isAlreadySlowed = false;
|
||||
for (auto it = game.m_effects.begin(); it != game.m_effects.end();) {
|
||||
if ((*it)->effectId == _EFFECT_SLOWING_ID) {
|
||||
(*it)->mDuration += mDuration;
|
||||
isAlreadySlowed = true;
|
||||
break;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
if (!isAlreadySlowed) {
|
||||
game.m_effects.push_back(new SlowingEffect(game.m_time, mDuration));
|
||||
game.m_effects.back()->activate(game);
|
||||
}
|
||||
}
|
||||
|
||||
/* =============
|
||||
* ===Effects===
|
||||
* =============
|
||||
* */
|
||||
|
||||
Effect::Effect(char id, double start_time, double duration) : effectId(id), mStartTime(start_time), mDuration(duration) {};
|
||||
|
||||
bool Effect::isExpired(double time) {
|
||||
if (mStartTime + mDuration > time)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
SlowingEffect::SlowingEffect(double start_time, double duration) : Effect(_EFFECT_SLOWING_ID, start_time, duration) {};
|
||||
|
||||
void SlowingEffect::activate(Arkanoid& game) {
|
||||
for (Ball& ball : game.m_balls) {
|
||||
ball.velocity = sf::Vector2f{ball.velocity.x * mSlowingFactor, ball.velocity.y * mSlowingFactor};
|
||||
}
|
||||
}
|
||||
void SlowingEffect::activate(Ball& ball) {
|
||||
ball.velocity = sf::Vector2f{ball.velocity.x * mSlowingFactor, ball.velocity.y * mSlowingFactor};
|
||||
}
|
||||
|
||||
void SlowingEffect::deactivate(Arkanoid& game) {
|
||||
for (Ball& ball : game.m_balls) {
|
||||
ball.velocity = sf::Vector2f{ball.velocity.x / mSlowingFactor, ball.velocity.y / mSlowingFactor};
|
||||
}
|
||||
}
|
||||
|
||||
92
term1/seminar13_polymorphism/arkanoid/bonus.hpp
Normal file
92
term1/seminar13_polymorphism/arkanoid/bonus.hpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
#include <SFML/Window.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
class Paddle;
|
||||
class Ball;
|
||||
class Arkanoid;
|
||||
|
||||
class Bonus
|
||||
{
|
||||
protected:
|
||||
inline static const float speed = 120;
|
||||
inline static const float radius = 15;
|
||||
|
||||
sf::Vector2f m_position;
|
||||
float m_time;
|
||||
|
||||
public:
|
||||
Bonus(sf::Vector2f position);
|
||||
void update(float dt);
|
||||
virtual void draw(sf::RenderWindow& window) const = 0;
|
||||
virtual void activate(Arkanoid& game) = 0;
|
||||
virtual ~Bonus() = default;
|
||||
|
||||
bool isColiding(const Paddle& paddle) const;
|
||||
// Класс Arkanoid должен быть дружественным, так как он может менять внутреннее объекта-бонуса
|
||||
friend class Arkanoid;
|
||||
};
|
||||
|
||||
class TripleBallBonus : public Bonus {
|
||||
public:
|
||||
TripleBallBonus(sf::Vector2f position): Bonus(position) {};
|
||||
void draw(sf::RenderWindow& window) const;
|
||||
void activate(Arkanoid& game);
|
||||
};
|
||||
|
||||
class EnlargePaddleBonus : public Bonus {
|
||||
public:
|
||||
EnlargePaddleBonus(sf::Vector2f position): Bonus(position) {};
|
||||
void draw(sf::RenderWindow& window) const;
|
||||
void activate(Arkanoid& game);
|
||||
};
|
||||
|
||||
class ShrinkPaddleBonus : public Bonus {
|
||||
public:
|
||||
ShrinkPaddleBonus(sf::Vector2f position): Bonus(position) {};
|
||||
void draw(sf::RenderWindow& window) const;
|
||||
void activate(Arkanoid& game);
|
||||
};
|
||||
|
||||
class SlowingBonus : public Bonus {
|
||||
private:
|
||||
double mDuration = 10;
|
||||
public:
|
||||
SlowingBonus(sf::Vector2f position): Bonus(position) {};
|
||||
void draw(sf::RenderWindow& window) const;
|
||||
void activate(Arkanoid& game);
|
||||
};
|
||||
|
||||
/*
|
||||
* Effects
|
||||
* */
|
||||
#define _EFFECT_SLOWING_ID 0
|
||||
class Effect {
|
||||
protected:
|
||||
char effectId;
|
||||
double mStartTime;
|
||||
double mDuration;
|
||||
public:
|
||||
Effect(char id, double start_time, double duration);
|
||||
virtual ~Effect() = default;
|
||||
|
||||
virtual void activate(Arkanoid& game) = 0;
|
||||
virtual void activate(Ball& ball) = 0;
|
||||
virtual void deactivate(Arkanoid& game) = 0;
|
||||
bool isExpired(double time);
|
||||
|
||||
|
||||
friend class SlowingBonus;
|
||||
friend class TripleBallBonus;
|
||||
friend class Arkanoid;
|
||||
};
|
||||
|
||||
class SlowingEffect : public Effect {
|
||||
private:
|
||||
float mSlowingFactor = 0.1;
|
||||
char id = _EFFECT_SLOWING_ID;
|
||||
public:
|
||||
SlowingEffect(double start_time, double duration);
|
||||
void activate(Arkanoid& game);
|
||||
void activate(Ball& ball);
|
||||
void deactivate(Arkanoid& game);
|
||||
};
|
||||
5
term1/seminar13_polymorphism/arkanoid/brick.hpp
Normal file
5
term1/seminar13_polymorphism/arkanoid/brick.hpp
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
struct Brick
|
||||
{
|
||||
bool isActive;
|
||||
};
|
||||
63
term1/seminar13_polymorphism/arkanoid/brick_grid.cpp
Normal file
63
term1/seminar13_polymorphism/arkanoid/brick_grid.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#include <vector>
|
||||
#include "brick_grid.hpp"
|
||||
|
||||
BrickGrid::BrickGrid() {}
|
||||
BrickGrid::BrickGrid(sf::FloatRect borders, int numBrickColumns, int numBrickRows) :
|
||||
m_border(borders),
|
||||
m_numBrickColumns(numBrickColumns),
|
||||
m_numBrickRows(numBrickRows),
|
||||
m_numActiveBricks(numBrickColumns * numBrickRows)
|
||||
{
|
||||
m_bricks.resize(m_numBrickColumns * m_numBrickRows, Brick{true});
|
||||
m_brickShape.setSize(getBrickSizes());
|
||||
m_brickShape.setOutlineColor(sf::Color::Black);
|
||||
m_brickShape.setOutlineThickness(0.5);
|
||||
m_brickShape.setFillColor(color);
|
||||
}
|
||||
|
||||
sf::FloatRect BrickGrid::getBorder() const
|
||||
{
|
||||
return m_border;
|
||||
}
|
||||
|
||||
sf::Vector2i BrickGrid::getGridSizes() const
|
||||
{
|
||||
return {m_numBrickColumns, m_numBrickRows};
|
||||
}
|
||||
|
||||
sf::Vector2f BrickGrid::getBrickSizes() const
|
||||
{
|
||||
return {m_border.width / m_numBrickColumns, m_border.height / m_numBrickRows};
|
||||
}
|
||||
|
||||
bool BrickGrid::isBrickActive(std::pair<int, int> indexes) const
|
||||
{
|
||||
return m_bricks[indexes.first + indexes.second * m_numBrickColumns].isActive;
|
||||
}
|
||||
|
||||
void BrickGrid::deactivateBrick(std::pair<int, int> indexes)
|
||||
{
|
||||
m_bricks[indexes.first + indexes.second * m_numBrickColumns].isActive = false;
|
||||
m_numActiveBricks--;
|
||||
}
|
||||
|
||||
int BrickGrid::getNumActiveBricks() const
|
||||
{
|
||||
return m_numActiveBricks;
|
||||
}
|
||||
|
||||
void BrickGrid::draw(sf::RenderWindow& window)
|
||||
{
|
||||
auto [brickWidth, brickHeight] = getBrickSizes();
|
||||
|
||||
for (int j = 0; j < m_numBrickRows; ++j)
|
||||
{
|
||||
for (int i = 0; i < m_numBrickColumns; ++i)
|
||||
{
|
||||
if (!isBrickActive({i, j}))
|
||||
continue;
|
||||
m_brickShape.setPosition({m_border.left + i * brickWidth, m_border.top + j * brickHeight});
|
||||
window.draw(m_brickShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
term1/seminar13_polymorphism/arkanoid/brick_grid.hpp
Normal file
37
term1/seminar13_polymorphism/arkanoid/brick_grid.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
#include <SFML/Window.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include "brick.hpp"
|
||||
|
||||
class BrickGrid
|
||||
{
|
||||
private:
|
||||
inline static const sf::Color color {100, 200, 250};
|
||||
|
||||
sf::FloatRect m_border;
|
||||
int m_numBrickColumns;
|
||||
int m_numBrickRows;
|
||||
|
||||
std::vector<Brick> m_bricks;
|
||||
sf::RectangleShape m_brickShape;
|
||||
|
||||
int m_numActiveBricks;
|
||||
|
||||
public:
|
||||
BrickGrid();
|
||||
BrickGrid(sf::FloatRect borders, int numBrickColumns, int numBrickRows);
|
||||
|
||||
sf::FloatRect getBorder() const;
|
||||
|
||||
sf::Vector2i getGridSizes() const;
|
||||
|
||||
sf::Vector2f getBrickSizes() const;
|
||||
|
||||
bool isBrickActive(std::pair<int, int> indexes) const;
|
||||
|
||||
void deactivateBrick(std::pair<int, int> indexes);
|
||||
|
||||
int getNumActiveBricks() const;
|
||||
|
||||
void draw(sf::RenderWindow& window);
|
||||
};
|
||||
BIN
term1/seminar13_polymorphism/arkanoid/consola.ttf
Normal file
BIN
term1/seminar13_polymorphism/arkanoid/consola.ttf
Normal file
Binary file not shown.
73
term1/seminar13_polymorphism/arkanoid/main.cpp
Normal file
73
term1/seminar13_polymorphism/arkanoid/main.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#include <SFML/Window.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <list>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
|
||||
// Описываем все классы, которые мы будем использовать в программе
|
||||
// Это нужно сделать так как даже определение одного класса может зависеть от другого
|
||||
// Например, класс Bonus зависит от класса Arkanoid и наоборот
|
||||
struct Ball;
|
||||
struct Brick;
|
||||
struct Paddle;
|
||||
class Bonus;
|
||||
class BrickGrid;
|
||||
class Arkanoid;
|
||||
|
||||
#include "paddle.hpp"
|
||||
#include "brick_grid.hpp"
|
||||
#include "ball.hpp"
|
||||
#include "bonus.hpp"
|
||||
#include "arkanoid.hpp"
|
||||
|
||||
int main ()
|
||||
{
|
||||
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;
|
||||
std::exit(1);
|
||||
}
|
||||
Arkanoid game({0, 0, 1000, 800}, font);
|
||||
|
||||
|
||||
while (window.isOpen())
|
||||
{
|
||||
float dt = clock.restart().asSeconds();
|
||||
//std::cout << "FPS=" << static_cast<int>(1.0 / dt) << "\n";
|
||||
|
||||
// Обработка событий
|
||||
sf::Event event;
|
||||
while(window.pollEvent(event))
|
||||
{
|
||||
if(event.type == sf::Event::Closed || (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape))
|
||||
{
|
||||
window.close();
|
||||
}
|
||||
if (event.type == sf::Event::MouseButtonPressed)
|
||||
{
|
||||
game.onMousePressed(event);
|
||||
}
|
||||
}
|
||||
window.clear(sf::Color(0, 0, 0));
|
||||
// Расчитываем новые координаты и новую скорость шарика
|
||||
game.update(window, dt);
|
||||
game.draw(window);
|
||||
|
||||
// Отображам всё нарисованное на временном "холсте" на экран
|
||||
window.display();
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
18
term1/seminar13_polymorphism/arkanoid/paddle.cpp
Normal file
18
term1/seminar13_polymorphism/arkanoid/paddle.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include "paddle.hpp"
|
||||
|
||||
Paddle::Paddle() {}
|
||||
Paddle::Paddle(sf::Vector2f position, sf::Vector2f size) : position(position), size(size) {}
|
||||
|
||||
sf::FloatRect Paddle::getBorder() const
|
||||
{
|
||||
return {position.x - size.x / 2.0f, position.y - size.y / 2.0f, size.x, size.y};
|
||||
}
|
||||
|
||||
void Paddle::draw(sf::RenderWindow& window)
|
||||
{
|
||||
static sf::RectangleShape shape{};
|
||||
shape.setPosition(position - size / 2.0f);
|
||||
shape.setSize(size);
|
||||
shape.setFillColor(color);
|
||||
window.draw(shape);
|
||||
}
|
||||
19
term1/seminar13_polymorphism/arkanoid/paddle.hpp
Normal file
19
term1/seminar13_polymorphism/arkanoid/paddle.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
#include <SFML/Window.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
struct Paddle
|
||||
{
|
||||
inline static const sf::Color color {sf::Color::White};
|
||||
sf::Vector2f position;
|
||||
sf::Vector2f size;
|
||||
|
||||
Paddle();
|
||||
Paddle(sf::Vector2f position, sf::Vector2f size);
|
||||
|
||||
sf::FloatRect getBorder() const;
|
||||
|
||||
void draw(sf::RenderWindow& window);
|
||||
};
|
||||
|
||||
|
||||
11
term1/seminar13_polymorphism/arkanoid/tags
Normal file
11
term1/seminar13_polymorphism/arkanoid/tags
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
||||
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
||||
!_TAG_OUTPUT_EXCMD mixed /number, pattern, mixed, or combineV2/
|
||||
!_TAG_OUTPUT_FILESEP slash /slash or backslash/
|
||||
!_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/
|
||||
!_TAG_PATTERN_LENGTH_LIMIT 96 /0 for no limit/
|
||||
!_TAG_PROC_CWD /home/nihonium/projects/mipt_cpp/seminar13_polymorphism/arkanoid/ //
|
||||
!_TAG_PROGRAM_AUTHOR Universal Ctags Team //
|
||||
!_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/
|
||||
!_TAG_PROGRAM_URL https://ctags.io/ /official site/
|
||||
!_TAG_PROGRAM_VERSION 5.9.0 /p5.9.20220828.0/
|
||||
BIN
term1/seminar13_polymorphism/homework_polymorphism.pdf
Normal file
BIN
term1/seminar13_polymorphism/homework_polymorphism.pdf
Normal file
Binary file not shown.
Reference in a new issue