2014-09-30 22:07:25 +08:00
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
// Headers
|
|
|
|
////////////////////////////////////////////////////////////
|
2022-07-05 00:20:58 +08:00
|
|
|
#include <SFML/Graphics.hpp>
|
|
|
|
|
2023-04-24 20:13:52 +08:00
|
|
|
#include <SFML/Audio.hpp>
|
|
|
|
|
2024-02-08 21:28:57 +08:00
|
|
|
#include <filesystem>
|
2022-06-13 02:22:04 +08:00
|
|
|
#include <random>
|
2024-02-08 21:28:57 +08:00
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <cmath>
|
|
|
|
#include <cstdlib>
|
2014-09-30 22:07:25 +08:00
|
|
|
|
2018-04-13 02:39:55 +08:00
|
|
|
#ifdef SFML_SYSTEM_IOS
|
|
|
|
#include <SFML/Main.hpp>
|
|
|
|
#endif
|
|
|
|
|
2024-09-24 03:55:33 +08:00
|
|
|
namespace
|
|
|
|
{
|
2022-01-10 06:47:04 +08:00
|
|
|
std::filesystem::path resourcesDir()
|
2018-04-13 02:39:55 +08:00
|
|
|
{
|
|
|
|
#ifdef SFML_SYSTEM_IOS
|
|
|
|
return "";
|
|
|
|
#else
|
2022-03-15 09:32:26 +08:00
|
|
|
return "resources";
|
2018-04-13 02:39:55 +08:00
|
|
|
#endif
|
|
|
|
}
|
2024-09-24 03:55:33 +08:00
|
|
|
} // namespace
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
/// Entry point of application
|
|
|
|
///
|
|
|
|
/// \return Application exit code
|
|
|
|
///
|
|
|
|
////////////////////////////////////////////////////////////
|
|
|
|
int main()
|
|
|
|
{
|
2022-06-13 02:22:04 +08:00
|
|
|
std::random_device rd;
|
2022-07-05 00:20:58 +08:00
|
|
|
std::mt19937 rng(rd());
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
// Define some constants
|
2023-02-15 11:42:12 +08:00
|
|
|
const float gameWidth = 800;
|
|
|
|
const float gameHeight = 600;
|
|
|
|
const sf::Vector2f paddleSize(25, 100);
|
|
|
|
const float ballRadius = 10.f;
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
// Create the window of the application
|
2022-07-05 00:20:58 +08:00
|
|
|
sf::RenderWindow window(sf::VideoMode({static_cast<unsigned int>(gameWidth), static_cast<unsigned int>(gameHeight)}, 32),
|
|
|
|
"SFML Tennis",
|
2014-11-17 16:44:46 +08:00
|
|
|
sf::Style::Titlebar | sf::Style::Close);
|
2014-09-30 22:07:25 +08:00
|
|
|
window.setVerticalSyncEnabled(true);
|
|
|
|
|
|
|
|
// Load the sounds used in the game
|
2024-07-06 10:24:00 +08:00
|
|
|
const sf::SoundBuffer ballSoundBuffer(resourcesDir() / "ball.wav");
|
|
|
|
sf::Sound ballSound(ballSoundBuffer);
|
2014-09-30 22:07:25 +08:00
|
|
|
|
2020-12-13 04:40:52 +08:00
|
|
|
// Create the SFML logo texture:
|
2024-07-06 10:24:00 +08:00
|
|
|
const sf::Texture sfmlLogoTexture(resourcesDir() / "sfml_logo.png");
|
|
|
|
sf::Sprite sfmlLogo(sfmlLogoTexture);
|
2021-12-15 17:29:34 +08:00
|
|
|
sfmlLogo.setPosition({170.f, 50.f});
|
2020-12-13 04:40:52 +08:00
|
|
|
|
2014-09-30 22:07:25 +08:00
|
|
|
// Create the left paddle
|
|
|
|
sf::RectangleShape leftPaddle;
|
|
|
|
leftPaddle.setSize(paddleSize - sf::Vector2f(3, 3));
|
|
|
|
leftPaddle.setOutlineThickness(3);
|
|
|
|
leftPaddle.setOutlineColor(sf::Color::Black);
|
|
|
|
leftPaddle.setFillColor(sf::Color(100, 100, 200));
|
|
|
|
leftPaddle.setOrigin(paddleSize / 2.f);
|
|
|
|
|
|
|
|
// Create the right paddle
|
|
|
|
sf::RectangleShape rightPaddle;
|
|
|
|
rightPaddle.setSize(paddleSize - sf::Vector2f(3, 3));
|
|
|
|
rightPaddle.setOutlineThickness(3);
|
|
|
|
rightPaddle.setOutlineColor(sf::Color::Black);
|
|
|
|
rightPaddle.setFillColor(sf::Color(200, 100, 100));
|
|
|
|
rightPaddle.setOrigin(paddleSize / 2.f);
|
|
|
|
|
|
|
|
// Create the ball
|
|
|
|
sf::CircleShape ball;
|
|
|
|
ball.setRadius(ballRadius - 3);
|
2020-12-13 04:40:52 +08:00
|
|
|
ball.setOutlineThickness(2);
|
2014-09-30 22:07:25 +08:00
|
|
|
ball.setOutlineColor(sf::Color::Black);
|
|
|
|
ball.setFillColor(sf::Color::White);
|
2021-12-15 17:29:34 +08:00
|
|
|
ball.setOrigin({ballRadius / 2.f, ballRadius / 2.f});
|
2014-09-30 22:07:25 +08:00
|
|
|
|
2024-06-25 04:08:51 +08:00
|
|
|
// Open the text font
|
2024-07-06 10:24:00 +08:00
|
|
|
const sf::Font font(resourcesDir() / "tuffy.ttf");
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
// Initialize the pause message
|
2022-09-06 13:18:29 +08:00
|
|
|
sf::Text pauseMessage(font);
|
2014-09-30 22:07:25 +08:00
|
|
|
pauseMessage.setCharacterSize(40);
|
2021-12-15 17:29:34 +08:00
|
|
|
pauseMessage.setPosition({170.f, 200.f});
|
2015-12-29 21:14:43 +08:00
|
|
|
pauseMessage.setFillColor(sf::Color::White);
|
2020-12-13 04:40:52 +08:00
|
|
|
|
2022-07-05 00:20:58 +08:00
|
|
|
#ifdef SFML_SYSTEM_IOS
|
2020-12-13 04:40:52 +08:00
|
|
|
pauseMessage.setString("Welcome to SFML Tennis!\nTouch the screen to start the game.");
|
2022-07-05 00:20:58 +08:00
|
|
|
#else
|
2020-12-13 04:40:52 +08:00
|
|
|
pauseMessage.setString("Welcome to SFML Tennis!\n\nPress space to start the game.");
|
2022-07-05 00:20:58 +08:00
|
|
|
#endif
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
// Define the paddles properties
|
2022-10-24 06:53:36 +08:00
|
|
|
sf::Clock aiTimer;
|
|
|
|
const sf::Time aiTime = sf::seconds(0.1f);
|
2022-07-05 00:20:58 +08:00
|
|
|
const float paddleSpeed = 400.f;
|
|
|
|
float rightPaddleSpeed = 0.f;
|
|
|
|
const float ballSpeed = 400.f;
|
|
|
|
sf::Angle ballAngle = sf::degrees(0); // to be changed later
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
sf::Clock clock;
|
2022-07-05 00:20:58 +08:00
|
|
|
bool isPlaying = false;
|
2014-09-30 22:07:25 +08:00
|
|
|
while (window.isOpen())
|
|
|
|
{
|
|
|
|
// Handle events
|
2024-06-23 18:30:02 +08:00
|
|
|
while (const std::optional event = window.pollEvent())
|
2014-09-30 22:07:25 +08:00
|
|
|
{
|
|
|
|
// Window closed or escape key pressed: exit
|
2024-06-23 18:30:02 +08:00
|
|
|
if (event->is<sf::Event::Closed>() ||
|
|
|
|
(event->is<sf::Event::KeyPressed>() &&
|
|
|
|
event->getIf<sf::Event::KeyPressed>()->code == sf::Keyboard::Key::Escape))
|
2014-09-30 22:07:25 +08:00
|
|
|
{
|
|
|
|
window.close();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Space key pressed: play
|
2024-06-23 18:30:02 +08:00
|
|
|
if ((event->is<sf::Event::KeyPressed>() &&
|
|
|
|
event->getIf<sf::Event::KeyPressed>()->code == sf::Keyboard::Key::Space) ||
|
|
|
|
event->is<sf::Event::TouchBegan>())
|
2014-09-30 22:07:25 +08:00
|
|
|
{
|
|
|
|
if (!isPlaying)
|
|
|
|
{
|
|
|
|
// (re)start the game
|
|
|
|
isPlaying = true;
|
|
|
|
clock.restart();
|
|
|
|
|
|
|
|
// Reset the position of the paddles and ball
|
2021-12-15 17:29:34 +08:00
|
|
|
leftPaddle.setPosition({10.f + paddleSize.x / 2.f, gameHeight / 2.f});
|
|
|
|
rightPaddle.setPosition({gameWidth - 10.f - paddleSize.x / 2.f, gameHeight / 2.f});
|
|
|
|
ball.setPosition({gameWidth / 2.f, gameHeight / 2.f});
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
// Reset the ball angle
|
|
|
|
do
|
|
|
|
{
|
|
|
|
// Make sure the ball initial angle is not too much vertical
|
2022-06-13 02:22:04 +08:00
|
|
|
ballAngle = sf::degrees(std::uniform_real_distribution<float>(0, 360)(rng));
|
2022-07-05 00:20:58 +08:00
|
|
|
} while (std::abs(std::cos(ballAngle.asRadians())) < 0.7f);
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
}
|
2021-11-19 09:15:57 +08:00
|
|
|
|
2019-01-18 23:04:03 +08:00
|
|
|
// Window size changed, adjust view appropriately
|
2024-06-23 18:30:02 +08:00
|
|
|
if (event->is<sf::Event::Resized>())
|
2019-01-18 23:04:03 +08:00
|
|
|
{
|
|
|
|
sf::View view;
|
2022-03-30 08:53:17 +08:00
|
|
|
view.setSize({gameWidth, gameHeight});
|
2021-12-15 17:29:34 +08:00
|
|
|
view.setCenter({gameWidth / 2.f, gameHeight / 2.f});
|
2019-01-18 23:04:03 +08:00
|
|
|
window.setView(view);
|
|
|
|
}
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isPlaying)
|
|
|
|
{
|
2023-02-15 11:42:12 +08:00
|
|
|
const float deltaTime = clock.restart().asSeconds();
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
// Move the player's paddle
|
2023-12-20 06:45:33 +08:00
|
|
|
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Up) && (leftPaddle.getPosition().y - paddleSize.y / 2 > 5.f))
|
2014-09-30 22:07:25 +08:00
|
|
|
{
|
2021-12-15 17:29:34 +08:00
|
|
|
leftPaddle.move({0.f, -paddleSpeed * deltaTime});
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
2023-12-20 06:45:33 +08:00
|
|
|
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Down) &&
|
2022-07-05 00:20:58 +08:00
|
|
|
(leftPaddle.getPosition().y + paddleSize.y / 2 < gameHeight - 5.f))
|
2014-09-30 22:07:25 +08:00
|
|
|
{
|
2021-12-15 17:29:34 +08:00
|
|
|
leftPaddle.move({0.f, paddleSpeed * deltaTime});
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
2021-11-19 09:15:57 +08:00
|
|
|
|
2018-04-14 01:22:20 +08:00
|
|
|
if (sf::Touch::isDown(0))
|
|
|
|
{
|
2023-02-15 11:42:12 +08:00
|
|
|
const sf::Vector2i pos = sf::Touch::getPosition(0);
|
|
|
|
const sf::Vector2f mappedPos = window.mapPixelToCoords(pos);
|
2021-12-15 17:29:34 +08:00
|
|
|
leftPaddle.setPosition({leftPaddle.getPosition().x, mappedPos.y});
|
2018-04-14 01:22:20 +08:00
|
|
|
}
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
// Move the computer's paddle
|
|
|
|
if (((rightPaddleSpeed < 0.f) && (rightPaddle.getPosition().y - paddleSize.y / 2 > 5.f)) ||
|
|
|
|
((rightPaddleSpeed > 0.f) && (rightPaddle.getPosition().y + paddleSize.y / 2 < gameHeight - 5.f)))
|
|
|
|
{
|
2021-12-15 17:29:34 +08:00
|
|
|
rightPaddle.move({0.f, rightPaddleSpeed * deltaTime});
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update the computer's paddle direction according to the ball position
|
2022-10-24 06:53:36 +08:00
|
|
|
if (aiTimer.getElapsedTime() > aiTime)
|
2014-09-30 22:07:25 +08:00
|
|
|
{
|
2022-10-24 06:53:36 +08:00
|
|
|
aiTimer.restart();
|
2014-09-30 22:07:25 +08:00
|
|
|
if (ball.getPosition().y + ballRadius > rightPaddle.getPosition().y + paddleSize.y / 2)
|
|
|
|
rightPaddleSpeed = paddleSpeed;
|
|
|
|
else if (ball.getPosition().y - ballRadius < rightPaddle.getPosition().y - paddleSize.y / 2)
|
|
|
|
rightPaddleSpeed = -paddleSpeed;
|
|
|
|
else
|
|
|
|
rightPaddleSpeed = 0.f;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move the ball
|
2022-04-29 23:37:29 +08:00
|
|
|
ball.move({ballSpeed * deltaTime, ballAngle});
|
2014-09-30 22:07:25 +08:00
|
|
|
|
2022-07-05 00:20:58 +08:00
|
|
|
#ifdef SFML_SYSTEM_IOS
|
2020-12-13 04:40:52 +08:00
|
|
|
const std::string inputString = "Touch the screen to restart.";
|
2022-07-05 00:20:58 +08:00
|
|
|
#else
|
2020-12-13 04:40:52 +08:00
|
|
|
const std::string inputString = "Press space to restart or\nescape to exit.";
|
2022-07-05 00:20:58 +08:00
|
|
|
#endif
|
2021-11-19 09:15:57 +08:00
|
|
|
|
2014-09-30 22:07:25 +08:00
|
|
|
// Check collisions between the ball and the screen
|
|
|
|
if (ball.getPosition().x - ballRadius < 0.f)
|
|
|
|
{
|
|
|
|
isPlaying = false;
|
2020-12-13 04:40:52 +08:00
|
|
|
pauseMessage.setString("You Lost!\n\n" + inputString);
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
if (ball.getPosition().x + ballRadius > gameWidth)
|
|
|
|
{
|
|
|
|
isPlaying = false;
|
2020-12-13 04:40:52 +08:00
|
|
|
pauseMessage.setString("You Won!\n\n" + inputString);
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
if (ball.getPosition().y - ballRadius < 0.f)
|
|
|
|
{
|
|
|
|
ballSound.play();
|
|
|
|
ballAngle = -ballAngle;
|
2021-12-15 17:29:34 +08:00
|
|
|
ball.setPosition({ball.getPosition().x, ballRadius + 0.1f});
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
if (ball.getPosition().y + ballRadius > gameHeight)
|
|
|
|
{
|
|
|
|
ballSound.play();
|
|
|
|
ballAngle = -ballAngle;
|
2021-12-15 17:29:34 +08:00
|
|
|
ball.setPosition({ball.getPosition().x, gameHeight - ballRadius - 0.1f});
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
|
2022-06-13 02:22:04 +08:00
|
|
|
std::uniform_real_distribution<float> dist(0, 20);
|
|
|
|
|
2014-09-30 22:07:25 +08:00
|
|
|
// Check the collisions between the ball and the paddles
|
|
|
|
// Left Paddle
|
2015-04-01 10:08:23 +08:00
|
|
|
if (ball.getPosition().x - ballRadius < leftPaddle.getPosition().x + paddleSize.x / 2 &&
|
2014-09-30 22:07:25 +08:00
|
|
|
ball.getPosition().x - ballRadius > leftPaddle.getPosition().x &&
|
|
|
|
ball.getPosition().y + ballRadius >= leftPaddle.getPosition().y - paddleSize.y / 2 &&
|
|
|
|
ball.getPosition().y - ballRadius <= leftPaddle.getPosition().y + paddleSize.y / 2)
|
|
|
|
{
|
|
|
|
if (ball.getPosition().y > leftPaddle.getPosition().y)
|
2022-06-13 02:22:04 +08:00
|
|
|
ballAngle = sf::degrees(180) - ballAngle + sf::degrees(dist(rng));
|
2014-09-30 22:07:25 +08:00
|
|
|
else
|
2022-06-13 02:22:04 +08:00
|
|
|
ballAngle = sf::degrees(180) - ballAngle - sf::degrees(dist(rng));
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
ballSound.play();
|
2021-12-15 17:29:34 +08:00
|
|
|
ball.setPosition({leftPaddle.getPosition().x + ballRadius + paddleSize.x / 2 + 0.1f, ball.getPosition().y});
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Right Paddle
|
|
|
|
if (ball.getPosition().x + ballRadius > rightPaddle.getPosition().x - paddleSize.x / 2 &&
|
|
|
|
ball.getPosition().x + ballRadius < rightPaddle.getPosition().x &&
|
|
|
|
ball.getPosition().y + ballRadius >= rightPaddle.getPosition().y - paddleSize.y / 2 &&
|
|
|
|
ball.getPosition().y - ballRadius <= rightPaddle.getPosition().y + paddleSize.y / 2)
|
|
|
|
{
|
|
|
|
if (ball.getPosition().y > rightPaddle.getPosition().y)
|
2022-06-13 02:22:04 +08:00
|
|
|
ballAngle = sf::degrees(180) - ballAngle + sf::degrees(dist(rng));
|
2014-09-30 22:07:25 +08:00
|
|
|
else
|
2022-06-13 02:22:04 +08:00
|
|
|
ballAngle = sf::degrees(180) - ballAngle - sf::degrees(dist(rng));
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
ballSound.play();
|
2021-12-15 17:29:34 +08:00
|
|
|
ball.setPosition({rightPaddle.getPosition().x - ballRadius - paddleSize.x / 2 - 0.1f, ball.getPosition().y});
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the window
|
2020-12-13 04:40:52 +08:00
|
|
|
window.clear(sf::Color(50, 50, 50));
|
2014-09-30 22:07:25 +08:00
|
|
|
|
|
|
|
if (isPlaying)
|
|
|
|
{
|
|
|
|
// Draw the paddles and the ball
|
|
|
|
window.draw(leftPaddle);
|
|
|
|
window.draw(rightPaddle);
|
|
|
|
window.draw(ball);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Draw the pause message
|
|
|
|
window.draw(pauseMessage);
|
2020-12-13 04:40:52 +08:00
|
|
|
window.draw(sfmlLogo);
|
2014-09-30 22:07:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Display things on screen
|
|
|
|
window.display();
|
|
|
|
}
|
|
|
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|