diff --git a/.gitmodules b/.gitmodules index b0e3e35..a7dd850 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "vendor"] path = vendor - url = ssh://git@git.josephaquino.net:2222/joseph-aquino/imgui-sfml-premake.git + url = https://gitlab.com/JosephA1997/imgui-sfml-premake.git diff --git a/assets/fonts/ChakraPetch-Regular.ttf b/assets/fonts/ChakraPetch-Regular.ttf new file mode 100644 index 0000000..245df02 Binary files /dev/null and b/assets/fonts/ChakraPetch-Regular.ttf differ diff --git a/assets/fonts/OFL.txt b/assets/fonts/OFL.txt new file mode 100644 index 0000000..7bd926c --- /dev/null +++ b/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2018 The Chakra Petch Project Authors (https://github.com/m4rc1e/Chakra-Petch.git) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/sounds/bgMusic.wav b/assets/sounds/bgMusic.wav new file mode 100644 index 0000000..b21be06 Binary files /dev/null and b/assets/sounds/bgMusic.wav differ diff --git a/assets/sounds/hit.wav b/assets/sounds/hit.wav new file mode 100644 index 0000000..4110d3e Binary files /dev/null and b/assets/sounds/hit.wav differ diff --git a/assets/sounds/lose.ogg b/assets/sounds/lose.ogg new file mode 100644 index 0000000..0791ebc Binary files /dev/null and b/assets/sounds/lose.ogg differ diff --git a/assets/sounds/score.wav b/assets/sounds/score.wav new file mode 100644 index 0000000..c7debc6 Binary files /dev/null and b/assets/sounds/score.wav differ diff --git a/assets/sounds/win.ogg b/assets/sounds/win.ogg new file mode 100644 index 0000000..2c1b22e Binary files /dev/null and b/assets/sounds/win.ogg differ diff --git a/include/Game.h b/include/Game.h new file mode 100644 index 0000000..ec5230d --- /dev/null +++ b/include/Game.h @@ -0,0 +1,109 @@ +#pragma once + +#include + +#include +#include + +using TimePoint = std::chrono::steady_clock::time_point; +using TimeDuration = std::chrono::duration; + +struct Player +{ + sf::RectangleShape body{}; + + sf::Vector2f pos{}; + sf::Vector2f prevPos{}; + float velocity{5}; + unsigned int score{}; + bool up{false}; + bool down{false}; +}; + +struct Cpu +{ + sf::RectangleShape body{}; + + sf::Vector2f pos{}; + sf::Vector2f prevPos{}; + float velocity{}; + unsigned int score{}; +}; + +struct Ball +{ + sf::CircleShape body{}; + + sf::Vector2f pos{}; + sf::Vector2f prevPos{}; + sf::Vector2f velocity{}; + float speed{}; + sf::Angle angle{}; +}; + +class Game +{ +public: + void init(); + + void run(); + + void getInput(); + + void render(); + + void updateCpu(); + + void updatePlayer(); + + void updateBall(); + + void collision(); + + void updateTexts(); + + void soundSystem(); + + void resetGame(); + +private: + sf::RenderWindow window {sf::VideoMode({1280u, 720u}), "project-pong"}; + std::chrono::steady_clock clock; + + sf::RectangleShape midline{}; + sf::RectangleShape gameBoundary{}; + + sf::Font font{}; + + sf::Text playerScore{font}; + sf::Text cpuScore{font}; + sf::Text time{font}; + + sf::SoundBuffer bgMusicBuffer; + sf::SoundBuffer failSoundBuffer; + sf::SoundBuffer winSoundBuffer; + sf::SoundBuffer hitSoundBuffer; + sf::SoundBuffer scoreSoundBuffer; + + sf::Sound bgMusic{bgMusicBuffer}; + sf::Sound failSound{failSoundBuffer}; + sf::Sound winSound{winSoundBuffer}; + sf::Sound hitSound{hitSoundBuffer}; + sf::Sound scoreSound{scoreSoundBuffer}; + + size_t frameCount{}; + + Player player{}; + Ball ball{}; + Cpu cpu{}; + + const double physicsDeltaTime = 1.0 / 60.0; + const double targetFps = 60.0; + const double targetFrameTime = 1.0 / targetFps; + + double accumulator{}; + + double playTime{}; + + +}; \ No newline at end of file diff --git a/include/util.h b/include/util.h new file mode 100644 index 0000000..011f79e --- /dev/null +++ b/include/util.h @@ -0,0 +1,6 @@ +#pragma once + +#include + + +sf::Vector2f getOverlap(const sf::FloatRect first, const sf::FloatRect second); diff --git a/scripts/Create-Solution.bat b/scripts/Create-Solution.bat new file mode 100644 index 0000000..d0a2509 --- /dev/null +++ b/scripts/Create-Solution.bat @@ -0,0 +1,4 @@ +@ECHO OFF +ECHO Project files will be written to ./build +..\vendor\premake5\premake5.exe vs2022 +PAUSE \ No newline at end of file diff --git a/scripts/build-ninja.sh b/scripts/build-ninja.sh new file mode 100755 index 0000000..4adada0 --- /dev/null +++ b/scripts/build-ninja.sh @@ -0,0 +1,9 @@ +#! /bin/bash +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "$SCRIPT_DIR" + +echo build files will be placed in /build +../vendor/premake5/premake5 premake-ninja && ninja $1 -C ../build + diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..89ee0db --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,8 @@ +#! /bin/bash +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "$SCRIPT_DIR" + +echo build files will be placed in ./build +./vendor/premake5/premake5 gmake2 && make -C ../build config=$1 diff --git a/scripts/clean-ninja.sh b/scripts/clean-ninja.sh new file mode 100755 index 0000000..0078286 --- /dev/null +++ b/scripts/clean-ninja.sh @@ -0,0 +1,11 @@ +#! /bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "$SCRIPT_DIR" + +if [ -z "$1" ] || [ $# -eq 0 ] + then + ninja -C ../build -t clean + else + ninja -C ../build -t clean $1 +fi \ No newline at end of file diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 0000000..4095c54 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,11 @@ +#! /bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "$SCRIPT_DIR" + +if [ -z "$1" ] || [ $# -eq 0 ] + then + make -C ../build config=Debug clean && make -C ../build config=Release clean + else + make -C ../build config=$1 clean +fi \ No newline at end of file diff --git a/scripts/ecc.sh b/scripts/ecc.sh new file mode 100755 index 0000000..7d2504e --- /dev/null +++ b/scripts/ecc.sh @@ -0,0 +1,14 @@ +#! /bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "$SCRIPT_DIR" + +if [ -z "$1" ] || [ $# -eq 0 ] + then + ../vendor/premake5/premake5 --config=Debug ecc + else + ../vendor/premake5/premake5 --config=$1 ecc +fi + + +mv compile_commands.json ../compile_commands.json \ No newline at end of file diff --git a/scripts/premake5.lua b/scripts/premake5.lua new file mode 100644 index 0000000..d65f286 --- /dev/null +++ b/scripts/premake5.lua @@ -0,0 +1,65 @@ + + +package.path = package.path .. ";../vendor/premake-scripts/?.lua" + +require "ecc/ecc" +require "ninja/ninja" + +local ogg = require("build-ogg") +local sfml = require("build-sfml") +local flac = require("build-flac") +local vorbis = require("build-vorbis") +local freetype = require("build-freetype") + +local pong = require("project") + +local rootdir = "../" + +workspace "pong" + architecture "x64" + startproject"pong" + configurations{"Debug", "Release"} + location (rootdir .. "build") + + filter"system:linux" + staticruntime"Off" + + filter"system:windows" + staticruntime"On" + filter"" + + filter"system:linux" + pic"On" -- fix warning when statically linking + filter"" + + + output_dir = "%{cfg.buildcfg}-%{cfg.system}-%{cfg.architecture}" + intdir = rootdir .. "intermediate-files/" + bindir = rootdir .. "bin/" .. output_dir + liboutdir = rootdir.. "lib/" .. output_dir + + externalwarnings "Off" + + filter "configurations:debug" + defines {"LOG_ENABLE", "GAME_DEBUG"} + symbols "on" + runtime "Debug" + warnings "Extra" + + filter "configurations:release" + defines {"GAME_RELEASE"} + optimize "Speed" + inlining "Auto" + symbols "off" + runtime "Release" + filter"" + + group"Dependencies" + ogg.generateproject(liboutdir, intdir) + sfml.generateproject(liboutdir, intdir) + flac.generateproject(liboutdir, intdir) + vorbis.generateproject(liboutdir, intdir) + freetype.generateproject(liboutdir, intdir) + group"" + + pong.generateproject(bindir, intdir) diff --git a/scripts/project.lua b/scripts/project.lua new file mode 100644 index 0000000..d07918e --- /dev/null +++ b/scripts/project.lua @@ -0,0 +1,52 @@ +local m = {} + +local rootdir = path.join(path.getabsolute(path.getdirectory(_SCRIPT)), "../") + +package.path = package.path .. ";../third-party/premake-scripts/?.lua" + +local sfml = require("build-sfml") + +function m.generateproject(bindir, intdir) +project "pong" + language "C++" + cppdialect "C++20" + systemversion "latest" + kind "WindowedApp" + targetname "pong" + targetdir (bindir) + debugdir (rootdir) + objdir (intdir) + + + files + { + path.join(rootdir, "src/**.cpp"), + path.join(rootdir, "include/**.h"), + path.join(rootdir, "include/**.hpp"), + } + + includedirs + { + path.join(rootdir, "src"), + path.join(rootdir, "include"), + } + + sfml.link() + + --windows specific settings-- + filter{"system:windows"} + defines {"PLATFORM_WINDOWS"} + + filter {"system:windows", "configurations:debug"} + defines{"_DEBUG", "_CONSOLE"} + + filter {"system:windows", "configurations:release"} + defines{"NDEBUG"} + + --linux specific settings-- + filter {"system:linux"} + defines {"PLATFORM_LINUX"} + +end + +return m \ No newline at end of file diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100755 index 0000000..fdddd9a --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "$SCRIPT_DIR/.." + +./bin/$1-linux-x86_64/snake diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..dfcb3ae --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,277 @@ +#include "Game.h" + +#include +#include +#include +#include + +#include "util.h" + +sf::Vector2f velocityInDirection(const float speed_in, const sf::Angle angle_in) +{ + return sf::Vector2f{speed_in * std::cos(angle_in.asRadians()), speed_in * std::sin(angle_in.asRadians())}; +} + +void Game::init() +{ + //load assets + if (!font.openFromFile("assets/fonts/ChakraPetch-Regular.ttf")) + { + std::cerr << "Error: cannot open 'assets/fonts/ChakraPetch-Regular.ttf'\n"; + } + + if (!bgMusicBuffer.loadFromFile("assets/sounds/bgMusic.wav")) + { + std::cerr << "Error: cannot open 'assets/sounds/bgMusic.wav'\n"; + } + + if (!winSoundBuffer.loadFromFile("assets/sounds/win.ogg")) + { + std::cerr << "Error: cannot open 'assets/sounds/win.ogg'\n"; + } + + if (!failSoundBuffer.loadFromFile("assets/sounds/lose.ogg")) + { + std::cerr << "Error: cannot open 'assets/sounds/lose.ogg'\n"; + } + + if (!hitSoundBuffer.loadFromFile("assets/sounds/hit.wav")) + { + std::cerr << "Error: cannot open 'assets/sounds/hit.wav'\n"; + } + + if (!scoreSoundBuffer.loadFromFile("assets/sounds/score.wav")) + { + std::cerr << "Error: cannot open 'assets/sounds/score.wav'\n"; + } + + player.pos = {100, 100}; + player.body.setSize({10, 70}); + player.body.setPosition(player.pos); + player.body.setFillColor(sf::Color::White); + + cpu.pos = {(float)window.getSize().x - 100, 100}; + cpu.body.setSize({10,70}); + cpu.body.setPosition(cpu.pos); + cpu.body.setFillColor(sf::Color::White); + + ball.pos = {(float)window.getSize().x / 2.f, (float)window.getSize().y / 2.f}; + ball.body.setPosition(ball.pos); + ball.body.setFillColor(sf::Color::White); + ball.body.setRadius(5.f); + + gameBoundary.setOutlineThickness(5); + gameBoundary.setOutlineColor(sf::Color::White); + gameBoundary.setFillColor(sf::Color::Transparent); + gameBoundary.setPosition({0,100}); + gameBoundary.setSize({1280.f, 620.f}); + + midline.setFillColor(sf::Color::White); + midline.setSize({2, 40}); + midline.setPosition({(float)window.getSize().x/2.f - 1.f, 100.f }); + + + time.setString("Time: 00:00"); + time.setPosition({560, 25}); + time.setFillColor(sf::Color::White); + + bgMusic.setVolume(15.f); + failSound.setVolume(15.f); + winSound.setVolume(15.f); + hitSound.setVolume(15.f); + scoreSound.setVolume(15.f); + + bgMusic.play(); +} + +void Game::run() +{ + TimePoint lastFrameStartPoint = std::chrono::steady_clock::now(); + + while (window.isOpen()) + { + TimePoint currentFrameStartPoint = std::chrono::steady_clock::now(); + + TimeDuration duration = currentFrameStartPoint - lastFrameStartPoint; + const double frameTime = duration.count(); + + playTime += frameTime; + + lastFrameStartPoint = currentFrameStartPoint; + + accumulator += frameTime; + if (accumulator > 0.25) accumulator = 0.25; + + getInput(); + + if (bgMusic.getStatus() == sf::Sound::Status::Playing) + { + while (accumulator >= physicsDeltaTime) + { + updateBall(); + + updateCpu(); + + updatePlayer(); + + collision(); + + updateTexts(); + + accumulator -= physicsDeltaTime; + } + } + + soundSystem(); + + render(); + + TimePoint currentFrameEndPoint = std::chrono::steady_clock::now(); + TimeDuration currentFrameDuration = currentFrameEndPoint - currentFrameStartPoint; + + if (currentFrameDuration.count() < targetFrameTime) + + { + TimeDuration sleepNeeded = TimeDuration(targetFrameTime) - currentFrameDuration; + std::this_thread::sleep_for(std::chrono::duration_cast(sleepNeeded)); + } + } +} + +void Game::getInput() +{ + while (const std::optional event = window.pollEvent()) + { + if (event->is()) + { + window.close(); + } + + if (const auto* keyPressed = event->getIf()) + { + switch (keyPressed->scancode) + { + case sf::Keyboard::Scan::Escape: + window.close(); + break; + + case sf::Keyboard::Scan::Up: + case sf::Keyboard::Scan::W: + player.up = true; + break; + + case sf::Keyboard::Scan::Down: + case sf::Keyboard::Scan::S: + player.down = true; + break; + + case sf::Keyboard::Scan::R: + resetGame(); + break; + + default: + break; + } + } + + if (const auto* keyReleased = event->getIf()) + { + switch (keyReleased->scancode) + { + case sf::Keyboard::Scan::Up: + case sf::Keyboard::Scan::W: + player.up = false; + break; + + case sf::Keyboard::Scan::Down: + case sf::Keyboard::Scan::S: + player.down = false; + break; + + default: + break; + } + } + } +} + +void Game::render() +{ + window.clear(); + window.draw(gameBoundary); + + window.draw(midline); + + while (midline.getPosition().y <= window.getSize().y) + { + midline.move({0, 60}); + window.draw(midline); + } + + midline.setPosition({midline.getPosition().x, 100.f}); + + player.body.setPosition(player.pos); + window.draw(player.body); + + cpu.body.setPosition(cpu.pos); + window.draw(cpu.body); + + window.draw(playerScore); + window.draw(cpuScore); + window.draw(time); + + ball.body.setPosition(ball.pos); + window.draw(ball.body); + + window.display(); +} + +void Game::updateCpu() +{ + if (ball.pos.y > cpu.pos.y) + { + cpu.velocity = 5; + } + + if (ball.pos.y < cpu.pos.y) + { + cpu.velocity = -5; + } + + if (ball.pos.y == cpu.pos.y) + { + cpu.velocity = 0; + } + + cpu.prevPos = cpu.pos; + cpu.pos.y += cpu.velocity; +} +void Game::updatePlayer() +{ + const int direction = player.down - player.up; + + player.prevPos = player.pos; + player.pos.y += player.velocity * (float)direction; +} +void Game::updateBall() +{ + ball.prevPos = ball.pos; + ball.pos += ball.velocity; +} +void Game::collision() {} + +void Game::updateTexts() +{ + const int totalSeconds = static_cast(playTime); + const int minutes = totalSeconds / 60; + const int seconds = totalSeconds % 60; + + const std::string secondsStr = (seconds < 10) ? "0" + std::to_string(seconds) : std::to_string(seconds); + const std::string minutesStr = (minutes < 10) ? "0" + std::to_string(minutes) : std::to_string(minutes); + + time.setString("Time: " + minutesStr + ":" + secondsStr); +} + +void Game::soundSystem() {} +void Game::resetGame() {} + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..076e872 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,9 @@ +#include "Game.h" + +int main (int argc, const char** argv) +{ + Game game; + game.init(); + game.run(); + return 0; +} diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..8596bd6 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,12 @@ +#include "util.h" + + +sf::Vector2f getOverlap(const sf::FloatRect first, const sf::FloatRect second) +{ + const float deltaX = std::abs(second.position.x - first.position.x); + const float deltaY = std::abs(second.position.y - first.position.y); + + const float resultX = (first.size.x + second.size.x) - deltaX; + const float resultY = (first.size.y + second.size.y) - deltaY; + return {resultX, resultY}; +}