├── .gitignore ├── LICENSE ├── README.md ├── code ├── compile.sh ├── p01.cpp ├── p02.cpp ├── p03.cpp ├── p04.cpp ├── p05.cpp ├── p06.cpp ├── p07.cpp ├── p08.cpp ├── p09.cpp ├── p10.cpp └── p11.cpp ├── presentation.odp └── presentation.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | /.~lock.presentation.odp# 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2016 Vittorio Romeo 2 | AFL License page: http://opensource.org/licenses/AFL-3.0 3 | 4 | Academic Free License ("AFL") v. 3.0 This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: 5 | Licensed under the Academic Free License version 3.0 6 | 1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: 7 | a) to reproduce the Original Work in copies, either alone or as part of a collective work; 8 | b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; 9 | c) to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; 10 | d) to perform the Original Work publicly; and 11 | e) to display the Original Work publicly. 12 | 2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. 13 | 3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. 14 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. 15 | 5) External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). 16 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. 17 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. 18 | 8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. 19 | 9) Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). 20 | 10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. 21 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. 22 | 12) Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. 23 | 13) Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. 24 | 14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 25 | 15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. 26 | 16) Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cppcon2014 2 | ========== 3 | 4 | Repository for the slides and the code of my "Quick game development with C++11/C++14" CppCon 2014 talk. 5 | 6 | [The official video is available here.](https://www.youtube.com/watch?v=TC9zhufV_Z8) 7 | 8 | Thanks for attending and/or checking out this repository! -------------------------------------------------------------------------------- /code/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Simple "compile&run" script that links SFML 4 | 5 | clang++ -std=c++1y \ 6 | -lsfml-system -lsfml-window -lsfml-graphics \ 7 | "${@:2}" ./$1 -o /tmp/$1.temp && /tmp/$1.temp -------------------------------------------------------------------------------- /code/p01.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // Let's begin the development of our arkanoid clone. 6 | 7 | // We'll start by creating a blank window using the SFML library. 8 | // The window will obtain input and display the game graphics. 9 | 10 | // The module is required to deal with graphics. 11 | // It also includes common STL classes such as `std::vector` and 12 | // the module required for window management. 13 | #include 14 | 15 | // Let's define some constants for the window. 16 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 17 | 18 | int main() 19 | { 20 | // Now we'll create the window. 21 | 22 | // The class is named `sf::RenderWindow`, and the constructor 23 | // requires an `sf::Vector2u` (size of the window), and an 24 | // `std::string` (title of the window). 25 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 1"}; 26 | 27 | // Instead of explicitly specifying the `sf::Vector2u` type, 28 | // we used the {...} uniform initialization syntax. 29 | 30 | // We'll also set a limit to the framerate, ensuring that the 31 | // game logic will run at a constant speed. 32 | window.setFramerateLimit(60); 33 | 34 | // The next step is "keeping the window alive". 35 | // This is where the "game loop" comes into play. 36 | // {Info: game loop} 37 | 38 | while(true) 39 | { 40 | // Every iteration of this loop is a "frame" of our game. 41 | // We'll begin our frame by clearing the window from previously 42 | // drawn graphics. 43 | window.clear(sf::Color::Black); 44 | 45 | // Then we'll check the input state. In this case, if the 46 | // player presses the "Escape" key, we'll jump outside of the 47 | // loop, destroying the window and terminating the program. 48 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 49 | 50 | // Show the window's contents. 51 | window.display(); 52 | } 53 | 54 | return 0; 55 | } -------------------------------------------------------------------------------- /code/p02.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // In this code segment we'll start implementing the first 6 | // game object: the ball. We will create a class for it and 7 | // learn how to use SFML shapes to display and move the ball. 8 | 9 | #include 10 | 11 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 12 | 13 | // We'll define a class representing the ball entity. 14 | // The class will deal both with its logic and its rendering. 15 | class Ball 16 | { 17 | public: 18 | // Let's define some constants for the default values. 19 | static const sf::Color defColor; 20 | static constexpr float defRadius{10.f}; 21 | static constexpr float defVelocity{1.f}; 22 | 23 | // `sf::CircleShape` is an SFML class that represents 24 | // a circular shape. By specifying a radius and a position, 25 | // it is possible to draw a circle on a window. 26 | sf::CircleShape shape; 27 | 28 | // We'll need a vector to store the current ball velocity. 29 | // It will be initialized with the default velocity values. 30 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 31 | 32 | // Ball constructor: it takes the starting position of 33 | // ball as two float arguments. 34 | Ball(float mX, float mY) 35 | { 36 | // SFML uses a coordinate system with the origin in 37 | // the top-left corner of the window. 38 | // {Info: coordinate system} 39 | 40 | shape.setPosition(mX, mY); 41 | shape.setRadius(defRadius); 42 | shape.setFillColor(defColor); 43 | shape.setOrigin(defRadius, defRadius); 44 | } 45 | 46 | // In our design every "game object" will have an `update` method 47 | // and a `draw` method. 48 | 49 | // The `update` method will update the game object's logic. 50 | 51 | // The `draw` method will draw the game object on the screen. 52 | // It will take a reference to a `sf::RenderWindow` as a parameter 53 | // that will be our drawing target. 54 | 55 | void update() 56 | { 57 | // SFML's shape classes have a `move` method that takes 58 | // a velocity float vector as a parameter. 59 | // {Info: ball movement} 60 | shape.move(velocity); 61 | } 62 | 63 | void draw(sf::RenderWindow& mTarget) 64 | { 65 | // In the ball's draw method we simply ask the window to 66 | // draw the shape for us. 67 | mTarget.draw(shape); 68 | } 69 | }; 70 | 71 | // Static data members must be initialized outside of the class. 72 | const sf::Color Ball::defColor{sf::Color::Red}; 73 | 74 | int main() 75 | { 76 | // Let's create an instance of `Ball`, positioned in the center. 77 | Ball ball{wndWidth / 2.f, wndHeight / 2.f}; 78 | 79 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 2"}; 80 | window.setFramerateLimit(60); 81 | 82 | while(true) 83 | { 84 | window.clear(sf::Color::Black); 85 | 86 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 87 | 88 | // In the game loop, we need to update and draw all game objects. 89 | ball.update(); 90 | ball.draw(window); 91 | 92 | window.display(); 93 | } 94 | 95 | return 0; 96 | } -------------------------------------------------------------------------------- /code/p03.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // The ball moves! However, we need to find a way to stop it from 6 | // leaving the window's bounds. 7 | 8 | // By testing if the X coordinate of the ball exceeds the window's 9 | // width or is less than 0 we can check if the ball left the window 10 | // horizontally. The same principle applies for the vertical bounds. 11 | // {Info: ball vs window collision} 12 | 13 | #include 14 | 15 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 16 | 17 | class Ball 18 | { 19 | public: 20 | static const sf::Color defColor; 21 | static constexpr float defRadius{10.f}; 22 | static constexpr float defVelocity{8.f}; 23 | 24 | sf::CircleShape shape; 25 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 26 | 27 | Ball(float mX, float mY) 28 | { 29 | shape.setPosition(mX, mY); 30 | shape.setRadius(defRadius); 31 | shape.setFillColor(defColor); 32 | shape.setOrigin(defRadius, defRadius); 33 | } 34 | 35 | // We will need to get very often the ball's left/right/top/bottom 36 | // bounds. Let's define some simple getters to help us. 37 | float x() const noexcept { return shape.getPosition().x; } 38 | float y() const noexcept { return shape.getPosition().y; } 39 | float left() const noexcept { return x() - shape.getRadius(); } 40 | float right() const noexcept { return x() + shape.getRadius(); } 41 | float top() const noexcept { return y() - shape.getRadius(); } 42 | float bottom() const noexcept { return y() + shape.getRadius(); } 43 | 44 | void update() 45 | { 46 | // We need to keep the ball "inside the window". 47 | // The most common (and probably best) way of doing this, and 48 | // of dealing with any kind of collision detection, is moving 49 | // the object first, then checking if it's intersecting 50 | // something. 51 | // If the test is positive, we simply respond to the collision 52 | // by altering the object's position and/or velocity. 53 | 54 | // Therefore, we begin by moving the ball. 55 | shape.move(velocity); 56 | 57 | // After the ball has moved, it may be "outside the window". 58 | // We need to check every direction and respond by changing 59 | // the velocity. 60 | 61 | // If it's leaving towards the left, we need to set 62 | // horizontal velocity to a positive value (towards the right). 63 | if(left() < 0) velocity.x = defVelocity; 64 | 65 | // Otherwise, if it's leaving towards the right, we need to 66 | // set horizontal velocity to a negative value (towards the 67 | // left). 68 | else if(right() > wndWidth) 69 | velocity.x = -defVelocity; 70 | 71 | // The same idea can be applied for top/bottom collisions. 72 | if(top() < 0) 73 | velocity.y = defVelocity; 74 | else if(bottom() > wndHeight) 75 | velocity.y = -defVelocity; 76 | } 77 | 78 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 79 | }; 80 | 81 | const sf::Color Ball::defColor{sf::Color::Red}; 82 | 83 | int main() 84 | { 85 | Ball ball{wndWidth / 2.f, wndHeight / 2.f}; 86 | 87 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 3"}; 88 | window.setFramerateLimit(60); 89 | 90 | while(true) 91 | { 92 | window.clear(sf::Color::Black); 93 | 94 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 95 | 96 | ball.update(); 97 | ball.draw(window); 98 | 99 | window.display(); 100 | } 101 | 102 | return 0; 103 | } -------------------------------------------------------------------------------- /code/p04.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // We're still missing two important elements: the paddle 6 | // and the bricks. We'll start by implementing a player 7 | // controlled paddle in this code segment. 8 | 9 | #include 10 | 11 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 12 | 13 | class Ball 14 | { 15 | public: 16 | static const sf::Color defColor; 17 | static constexpr float defRadius{10.f}; 18 | static constexpr float defVelocity{8.f}; 19 | 20 | sf::CircleShape shape; 21 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 22 | 23 | Ball(float mX, float mY) 24 | { 25 | shape.setPosition(mX, mY); 26 | shape.setRadius(defRadius); 27 | shape.setFillColor(defColor); 28 | shape.setOrigin(defRadius, defRadius); 29 | } 30 | 31 | void update() 32 | { 33 | shape.move(velocity); 34 | solveBoundCollisions(); 35 | } 36 | 37 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 38 | 39 | float x() const noexcept { return shape.getPosition().x; } 40 | float y() const noexcept { return shape.getPosition().y; } 41 | float left() const noexcept { return x() - shape.getRadius(); } 42 | float right() const noexcept { return x() + shape.getRadius(); } 43 | float top() const noexcept { return y() - shape.getRadius(); } 44 | float bottom() const noexcept { return y() + shape.getRadius(); } 45 | 46 | private: 47 | // Some minor refactoring always helps readability. 48 | void solveBoundCollisions() noexcept 49 | { 50 | if(left() < 0) 51 | velocity.x = defVelocity; 52 | else if(right() > wndWidth) 53 | velocity.x = -defVelocity; 54 | 55 | if(top() < 0) 56 | velocity.y = defVelocity; 57 | else if(bottom() > wndHeight) 58 | velocity.y = -defVelocity; 59 | } 60 | }; 61 | 62 | const sf::Color Ball::defColor{sf::Color::Red}; 63 | 64 | // Like the ball, the `Paddle` class will represent a game object, 65 | // with its own `update` and `draw` methods. 66 | class Paddle 67 | { 68 | public: 69 | static const sf::Color defColor; 70 | static constexpr float defWidth{60.f}; 71 | static constexpr float defHeight{20.f}; 72 | static constexpr float defVelocity{8.f}; 73 | 74 | // This time we'll use a `sf::RectangleShape`. 75 | sf::RectangleShape shape; 76 | sf::Vector2f velocity; 77 | 78 | // As with the ball, we construct the paddle with 79 | // arguments for the initial position and initialize 80 | // the SFML rectangle shape. 81 | Paddle(float mX, float mY) 82 | { 83 | shape.setPosition(mX, mY); 84 | shape.setSize({defWidth, defHeight}); 85 | shape.setFillColor(defColor); 86 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 87 | } 88 | 89 | void update() 90 | { 91 | // Before moving the paddle, we'll process player input, 92 | // changing the paddle's velocity. 93 | processPlayerInput(); 94 | shape.move(velocity); 95 | } 96 | 97 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 98 | 99 | float x() const noexcept { return shape.getPosition().x; } 100 | float y() const noexcept { return shape.getPosition().y; } 101 | float width() const noexcept { return shape.getSize().x; } 102 | float height() const noexcept { return shape.getSize().y; } 103 | float left() const noexcept { return x() - width() / 2.f; } 104 | float right() const noexcept { return x() + width() / 2.f; } 105 | float top() const noexcept { return y() - height() / 2.f; } 106 | float bottom() const noexcept { return y() + height() / 2.f; } 107 | 108 | private: 109 | void processPlayerInput() 110 | { 111 | // We will change the paddle's velocity depending on what 112 | // the user is currently pressing on its keyboard: 113 | // * If the left arrow key is being pressed, we set X velocity 114 | // to a negative value. 115 | // * If the right arrow key is being pressed, we set X velocity 116 | // to a positive value. 117 | // * If no arrow keys are being pressed, we set X velocity to 118 | // zero. 119 | 120 | // To avoid making having the paddle go "outside the window", 121 | // we will only apply the above velocity changes if the paddle 122 | // is inside the window. 123 | 124 | // So, if the user is trying to move the paddle towards the 125 | // right, but the paddle has already "escaped" the window 126 | // in that direction, we won't change the velocity. 127 | 128 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left) && left() > 0) 129 | { 130 | velocity.x = -defVelocity; 131 | } 132 | else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right) && 133 | right() < wndWidth) 134 | { 135 | velocity.x = defVelocity; 136 | } 137 | else 138 | { 139 | velocity.x = 0; 140 | } 141 | } 142 | }; 143 | 144 | const sf::Color Paddle::defColor{sf::Color::Red}; 145 | 146 | int main() 147 | { 148 | Ball ball{wndWidth / 2.f, wndHeight / 2.f}; 149 | 150 | // Let's create a `Paddle` instance. 151 | Paddle paddle{wndWidth / 2, wndHeight - 50}; 152 | 153 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 4"}; 154 | window.setFramerateLimit(60); 155 | 156 | while(true) 157 | { 158 | window.clear(sf::Color::Black); 159 | 160 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 161 | 162 | // And let's update and draw it. 163 | 164 | ball.update(); 165 | paddle.update(); 166 | 167 | ball.draw(window); 168 | paddle.draw(window); 169 | 170 | window.display(); 171 | } 172 | 173 | return 0; 174 | } -------------------------------------------------------------------------------- /code/p05.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // In this code segment we'll deal with the interactions 6 | // between the ball and the paddle. We'll need to check 7 | // eventual collisions and respond to them. 8 | 9 | #include 10 | 11 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 12 | 13 | class Ball 14 | { 15 | public: 16 | static const sf::Color defColor; 17 | static constexpr float defRadius{10.f}; 18 | static constexpr float defVelocity{8.f}; 19 | 20 | sf::CircleShape shape; 21 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 22 | 23 | Ball(float mX, float mY) 24 | { 25 | shape.setPosition(mX, mY); 26 | shape.setRadius(defRadius); 27 | shape.setFillColor(defColor); 28 | shape.setOrigin(defRadius, defRadius); 29 | } 30 | 31 | void update() 32 | { 33 | shape.move(velocity); 34 | solveBoundCollisions(); 35 | } 36 | 37 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 38 | 39 | float x() const noexcept { return shape.getPosition().x; } 40 | float y() const noexcept { return shape.getPosition().y; } 41 | float left() const noexcept { return x() - shape.getRadius(); } 42 | float right() const noexcept { return x() + shape.getRadius(); } 43 | float top() const noexcept { return y() - shape.getRadius(); } 44 | float bottom() const noexcept { return y() + shape.getRadius(); } 45 | 46 | private: 47 | void solveBoundCollisions() noexcept 48 | { 49 | if(left() < 0) 50 | velocity.x = defVelocity; 51 | else if(right() > wndWidth) 52 | velocity.x = -defVelocity; 53 | 54 | if(top() < 0) 55 | velocity.y = defVelocity; 56 | else if(bottom() > wndHeight) 57 | velocity.y = -defVelocity; 58 | } 59 | }; 60 | 61 | const sf::Color Ball::defColor{sf::Color::Red}; 62 | 63 | class Paddle 64 | { 65 | public: 66 | static const sf::Color defColor; 67 | static constexpr float defWidth{60.f}; 68 | static constexpr float defHeight{20.f}; 69 | static constexpr float defVelocity{8.f}; 70 | 71 | sf::RectangleShape shape; 72 | sf::Vector2f velocity; 73 | 74 | Paddle(float mX, float mY) 75 | { 76 | shape.setPosition(mX, mY); 77 | shape.setSize({defWidth, defHeight}); 78 | shape.setFillColor(defColor); 79 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 80 | } 81 | 82 | void update() 83 | { 84 | processPlayerInput(); 85 | shape.move(velocity); 86 | } 87 | 88 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 89 | 90 | float x() const noexcept { return shape.getPosition().x; } 91 | float y() const noexcept { return shape.getPosition().y; } 92 | float width() const noexcept { return shape.getSize().x; } 93 | float height() const noexcept { return shape.getSize().y; } 94 | float left() const noexcept { return x() - width() / 2.f; } 95 | float right() const noexcept { return x() + width() / 2.f; } 96 | float top() const noexcept { return y() - height() / 2.f; } 97 | float bottom() const noexcept { return y() + height() / 2.f; } 98 | 99 | private: 100 | void processPlayerInput() 101 | { 102 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left) && left() > 0) 103 | velocity.x = -defVelocity; 104 | else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right) && 105 | right() < wndWidth) 106 | velocity.x = defVelocity; 107 | else 108 | velocity.x = 0; 109 | } 110 | }; 111 | 112 | const sf::Color Paddle::defColor{sf::Color::Red}; 113 | 114 | // Let's begin by defining a generic function template that 115 | // detects when two game objects collide. 116 | // The requirement for the types `T1` and `T2` is having the 117 | // `left()`, `right()`, `top()`, `bottom()` members. 118 | // Therefore, we can use this template function both on our `Ball` 119 | // and our `Paddle` class. 120 | template 121 | bool isIntersecting(const T1& mA, const T2& mB) noexcept 122 | { 123 | // {Info: AABB vs AABB collision} 124 | return mA.right() >= mB.left() && mA.left() <= mB.right() && 125 | mA.bottom() >= mB.top() && mA.top() <= mB.bottom(); 126 | } 127 | 128 | // Now, let's also define a function that will executed every game 129 | // frame. This function will check if a paddle and a ball are 130 | // colliding, and if they are it will resolve the collision by 131 | // making the ball go upwards and in the direction opposite to the 132 | // collision. 133 | void solvePaddleBallCollision(const Paddle& mPaddle, Ball& mBall) noexcept 134 | { 135 | // If there's no intersection, exit the function. 136 | if(!isIntersecting(mPaddle, mBall)) return; 137 | 138 | // Otherwise let's "push" the ball upwards. 139 | mBall.velocity.y = -Ball::defVelocity; 140 | 141 | // And let's direct it dependently on the position where the 142 | // paddle was hit. 143 | 144 | // If the ball's center was to the left of the paddle's center, 145 | // the ball will move towards the left. Otherwise, it will move 146 | // towards the right. 147 | // {Info: ball vs paddle collision} 148 | mBall.velocity.x = 149 | mBall.x() < mPaddle.x() ? -Ball::defVelocity : Ball::defVelocity; 150 | } 151 | 152 | int main() 153 | { 154 | Ball ball{wndWidth / 2.f, wndHeight / 2.f}; 155 | Paddle paddle{wndWidth / 2, wndHeight - 50}; 156 | 157 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 5"}; 158 | window.setFramerateLimit(60); 159 | 160 | while(true) 161 | { 162 | window.clear(sf::Color::Black); 163 | 164 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 165 | 166 | ball.update(); 167 | paddle.update(); 168 | 169 | // After updating the ball and the paddle, let's check and 170 | // resolve eventual collisions. 171 | solvePaddleBallCollision(paddle, ball); 172 | 173 | ball.draw(window); 174 | paddle.draw(window); 175 | 176 | window.display(); 177 | } 178 | 179 | return 0; 180 | } -------------------------------------------------------------------------------- /code/p06.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // Let's now implement the last fundamental game element: the bricks. 6 | // In this code segment we'll only create the class and "spawn" a 7 | // grid of bricks in the game world. 8 | 9 | #include 10 | 11 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 12 | 13 | class Ball 14 | { 15 | public: 16 | static const sf::Color defColor; 17 | static constexpr float defRadius{10.f}; 18 | static constexpr float defVelocity{8.f}; 19 | 20 | sf::CircleShape shape; 21 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 22 | 23 | Ball(float mX, float mY) 24 | { 25 | shape.setPosition(mX, mY); 26 | shape.setRadius(defRadius); 27 | shape.setFillColor(defColor); 28 | shape.setOrigin(defRadius, defRadius); 29 | } 30 | 31 | void update() 32 | { 33 | shape.move(velocity); 34 | solveBoundCollisions(); 35 | } 36 | 37 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 38 | 39 | float x() const noexcept { return shape.getPosition().x; } 40 | float y() const noexcept { return shape.getPosition().y; } 41 | float left() const noexcept { return x() - shape.getRadius(); } 42 | float right() const noexcept { return x() + shape.getRadius(); } 43 | float top() const noexcept { return y() - shape.getRadius(); } 44 | float bottom() const noexcept { return y() + shape.getRadius(); } 45 | 46 | private: 47 | void solveBoundCollisions() noexcept 48 | { 49 | if(left() < 0) 50 | velocity.x = defVelocity; 51 | else if(right() > wndWidth) 52 | velocity.x = -defVelocity; 53 | 54 | if(top() < 0) 55 | velocity.y = defVelocity; 56 | else if(bottom() > wndHeight) 57 | velocity.y = -defVelocity; 58 | } 59 | }; 60 | 61 | const sf::Color Ball::defColor{sf::Color::Red}; 62 | 63 | struct Paddle 64 | { 65 | public: 66 | static const sf::Color defColor; 67 | static constexpr float defWidth{60.f}; 68 | static constexpr float defHeight{20.f}; 69 | static constexpr float defVelocity{8.f}; 70 | 71 | sf::RectangleShape shape; 72 | sf::Vector2f velocity; 73 | 74 | Paddle(float mX, float mY) 75 | { 76 | shape.setPosition(mX, mY); 77 | shape.setSize({defWidth, defHeight}); 78 | shape.setFillColor(defColor); 79 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 80 | } 81 | 82 | void update() 83 | { 84 | processPlayerInput(); 85 | shape.move(velocity); 86 | } 87 | 88 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 89 | 90 | float x() const noexcept { return shape.getPosition().x; } 91 | float y() const noexcept { return shape.getPosition().y; } 92 | float width() const noexcept { return shape.getSize().x; } 93 | float height() const noexcept { return shape.getSize().y; } 94 | float left() const noexcept { return x() - width() / 2.f; } 95 | float right() const noexcept { return x() + width() / 2.f; } 96 | float top() const noexcept { return y() - height() / 2.f; } 97 | float bottom() const noexcept { return y() + height() / 2.f; } 98 | 99 | private: 100 | void processPlayerInput() 101 | { 102 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left) && left() > 0) 103 | velocity.x = -defVelocity; 104 | else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right) && 105 | right() < wndWidth) 106 | velocity.x = defVelocity; 107 | else 108 | velocity.x = 0; 109 | } 110 | }; 111 | 112 | const sf::Color Paddle::defColor{sf::Color::Red}; 113 | 114 | // The class for the `Brick` game object will be very similar 115 | // to the `Paddle` class. 116 | class Brick 117 | { 118 | public: 119 | static const sf::Color defColor; 120 | static constexpr float defWidth{60.f}; 121 | static constexpr float defHeight{20.f}; 122 | static constexpr float defVelocity{8.f}; 123 | 124 | sf::RectangleShape shape; 125 | 126 | // We'll add a `destroyed` bool value that will keep track 127 | // of the brick's status. If the brick has been hit by a 128 | // ball, `destroyed` will be set to true. In the game loop 129 | // we'll then remove all bricks marked as such. 130 | bool destroyed{false}; 131 | 132 | Brick(float mX, float mY) 133 | { 134 | shape.setPosition(mX, mY); 135 | shape.setSize({defWidth, defHeight}); 136 | shape.setFillColor(defColor); 137 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 138 | } 139 | 140 | void update() {} 141 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 142 | 143 | float x() const noexcept { return shape.getPosition().x; } 144 | float y() const noexcept { return shape.getPosition().y; } 145 | float width() const noexcept { return shape.getSize().x; } 146 | float height() const noexcept { return shape.getSize().y; } 147 | float left() const noexcept { return x() - width() / 2.f; } 148 | float right() const noexcept { return x() + width() / 2.f; } 149 | float top() const noexcept { return y() - height() / 2.f; } 150 | float bottom() const noexcept { return y() + height() / 2.f; } 151 | }; 152 | 153 | const sf::Color Brick::defColor{sf::Color::Yellow}; 154 | 155 | template 156 | bool isIntersecting(const T1& mA, const T2& mB) noexcept 157 | { 158 | return mA.right() >= mB.left() && mA.left() <= mB.right() && 159 | mA.bottom() >= mB.top() && mA.top() <= mB.bottom(); 160 | } 161 | 162 | void solvePaddleBallCollision(const Paddle& mPaddle, Ball& mBall) noexcept 163 | { 164 | if(!isIntersecting(mPaddle, mBall)) return; 165 | 166 | mBall.velocity.y = -Ball::defVelocity; 167 | mBall.velocity.x = 168 | mBall.x() < mPaddle.x() ? -Ball::defVelocity : Ball::defVelocity; 169 | } 170 | 171 | int main() 172 | { 173 | Ball ball{wndWidth / 2.f, wndHeight / 2.f}; 174 | Paddle paddle{wndWidth / 2, wndHeight - 50}; 175 | 176 | // As we need to have multiple bricks, we'll use an `std::vector` 177 | // to store them. 178 | std::vector bricks; 179 | 180 | // We'll also define some constant values for the grid-pattern 181 | // our bricks will be created in. 182 | 183 | constexpr int brkCountX{11}; // How many columns? 184 | constexpr int brkCountY{4}; // How many rows? 185 | constexpr int brkStartColumn{1}; // What column number to start at? 186 | constexpr int brkStartRow{2}; // What row number to start at? 187 | constexpr float brkSpacing{3}; // Spacing between adjacent bricks. 188 | constexpr float brkOffsetX{22.f}; // X offset for the grid pattern. 189 | 190 | // We fill up our vector via a 2D for loop, creating bricks 191 | // in a grid-like pattern on the screen. 192 | for(int iX{0}; iX < brkCountX; ++iX) 193 | for(int iY{0}; iY < brkCountY; ++iY) 194 | { 195 | float x{(iX + brkStartColumn) * (Brick::defWidth + brkSpacing)}; 196 | float y{(iY + brkStartRow) * (Brick::defHeight + brkSpacing)}; 197 | 198 | bricks.emplace_back(brkOffsetX + x, y); 199 | } 200 | 201 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 6"}; 202 | window.setFramerateLimit(60); 203 | 204 | while(true) 205 | { 206 | window.clear(sf::Color::Black); 207 | 208 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 209 | 210 | ball.update(); 211 | paddle.update(); 212 | 213 | // Let's not forget to update and draw every brick in 214 | // the game loop. 215 | for(auto& brick : bricks) brick.update(); 216 | 217 | solvePaddleBallCollision(paddle, ball); 218 | 219 | ball.draw(window); 220 | paddle.draw(window); 221 | for(auto& brick : bricks) brick.draw(window); 222 | 223 | window.display(); 224 | } 225 | 226 | return 0; 227 | } -------------------------------------------------------------------------------- /code/p07.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // To finish up our game logic we need to check and respond to 6 | // "brick vs ball" collisions. 7 | 8 | #include 9 | 10 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 11 | 12 | class Ball 13 | { 14 | public: 15 | static const sf::Color defColor; 16 | static constexpr float defRadius{10.f}; 17 | static constexpr float defVelocity{8.f}; 18 | 19 | sf::CircleShape shape; 20 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 21 | 22 | Ball(float mX, float mY) 23 | { 24 | shape.setPosition(mX, mY); 25 | shape.setRadius(defRadius); 26 | shape.setFillColor(defColor); 27 | shape.setOrigin(defRadius, defRadius); 28 | } 29 | 30 | void update() 31 | { 32 | shape.move(velocity); 33 | solveBoundCollisions(); 34 | } 35 | 36 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 37 | 38 | float x() const noexcept { return shape.getPosition().x; } 39 | float y() const noexcept { return shape.getPosition().y; } 40 | float left() const noexcept { return x() - shape.getRadius(); } 41 | float right() const noexcept { return x() + shape.getRadius(); } 42 | float top() const noexcept { return y() - shape.getRadius(); } 43 | float bottom() const noexcept { return y() + shape.getRadius(); } 44 | 45 | private: 46 | void solveBoundCollisions() noexcept 47 | { 48 | if(left() < 0) 49 | velocity.x = defVelocity; 50 | else if(right() > wndWidth) 51 | velocity.x = -defVelocity; 52 | 53 | if(top() < 0) 54 | velocity.y = defVelocity; 55 | else if(bottom() > wndHeight) 56 | velocity.y = -defVelocity; 57 | } 58 | }; 59 | 60 | const sf::Color Ball::defColor{sf::Color::Red}; 61 | 62 | class Paddle 63 | { 64 | public: 65 | static const sf::Color defColor; 66 | static constexpr float defWidth{60.f}; 67 | static constexpr float defHeight{20.f}; 68 | static constexpr float defVelocity{8.f}; 69 | 70 | sf::RectangleShape shape; 71 | sf::Vector2f velocity; 72 | 73 | Paddle(float mX, float mY) 74 | { 75 | shape.setPosition(mX, mY); 76 | shape.setSize({defWidth, defHeight}); 77 | shape.setFillColor(defColor); 78 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 79 | } 80 | 81 | void update() 82 | { 83 | processPlayerInput(); 84 | shape.move(velocity); 85 | } 86 | 87 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 88 | 89 | float x() const noexcept { return shape.getPosition().x; } 90 | float y() const noexcept { return shape.getPosition().y; } 91 | float width() const noexcept { return shape.getSize().x; } 92 | float height() const noexcept { return shape.getSize().y; } 93 | float left() const noexcept { return x() - width() / 2.f; } 94 | float right() const noexcept { return x() + width() / 2.f; } 95 | float top() const noexcept { return y() - height() / 2.f; } 96 | float bottom() const noexcept { return y() + height() / 2.f; } 97 | 98 | private: 99 | void processPlayerInput() 100 | { 101 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left) && left() > 0) 102 | velocity.x = -defVelocity; 103 | else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right) && 104 | right() < wndWidth) 105 | velocity.x = defVelocity; 106 | else 107 | velocity.x = 0; 108 | } 109 | }; 110 | 111 | const sf::Color Paddle::defColor{sf::Color::Red}; 112 | 113 | class Brick 114 | { 115 | public: 116 | static const sf::Color defColor; 117 | static constexpr float defWidth{60.f}; 118 | static constexpr float defHeight{20.f}; 119 | static constexpr float defVelocity{8.f}; 120 | 121 | sf::RectangleShape shape; 122 | bool destroyed{false}; 123 | 124 | Brick(float mX, float mY) 125 | { 126 | shape.setPosition(mX, mY); 127 | shape.setSize({defWidth, defHeight}); 128 | shape.setFillColor(defColor); 129 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 130 | } 131 | 132 | void update() {} 133 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 134 | 135 | float x() const noexcept { return shape.getPosition().x; } 136 | float y() const noexcept { return shape.getPosition().y; } 137 | float width() const noexcept { return shape.getSize().x; } 138 | float height() const noexcept { return shape.getSize().y; } 139 | float left() const noexcept { return x() - width() / 2.f; } 140 | float right() const noexcept { return x() + width() / 2.f; } 141 | float top() const noexcept { return y() - height() / 2.f; } 142 | float bottom() const noexcept { return y() + height() / 2.f; } 143 | }; 144 | 145 | const sf::Color Brick::defColor{sf::Color::Yellow}; 146 | 147 | template 148 | bool isIntersecting(const T1& mA, const T2& mB) noexcept 149 | { 150 | return mA.right() >= mB.left() && mA.left() <= mB.right() && 151 | mA.bottom() >= mB.top() && mA.top() <= mB.bottom(); 152 | } 153 | 154 | void solvePaddleBallCollision(const Paddle& mPaddle, Ball& mBall) noexcept 155 | { 156 | if(!isIntersecting(mPaddle, mBall)) return; 157 | 158 | mBall.velocity.y = -Ball::defVelocity; 159 | mBall.velocity.x = 160 | mBall.x() < mPaddle.x() ? -Ball::defVelocity : Ball::defVelocity; 161 | } 162 | 163 | // Here's the most complex part of our game: brick-ball collision. 164 | // We need to find out from what direction the ball hit the brick, 165 | // and respond accordingly. 166 | void solveBrickBallCollision(Brick& mBrick, Ball& mBall) noexcept 167 | { 168 | // If there's no intersection, exit the function. 169 | if(!isIntersecting(mBrick, mBall)) return; 170 | 171 | // Otherwise, the brick has been hit! Mark it. 172 | mBrick.destroyed = true; 173 | 174 | // Let's calculate how much the ball intersects the brick 175 | // in every direction. 176 | // {Info: ball vs brick collision} 177 | float overlapLeft{mBall.right() - mBrick.left()}; 178 | float overlapRight{mBrick.right() - mBall.left()}; 179 | float overlapTop{mBall.bottom() - mBrick.top()}; 180 | float overlapBottom{mBrick.bottom() - mBall.top()}; 181 | 182 | // If the magnitude of the left overlap is smaller than the 183 | // right one we can safely assume the ball hit the brick 184 | // from the left. 185 | bool ballFromLeft(std::abs(overlapLeft) < std::abs(overlapRight)); 186 | 187 | // We can apply the same idea for top/bottom collisions. 188 | bool ballFromTop(std::abs(overlapTop) < std::abs(overlapBottom)); 189 | 190 | // Let's store the minimum overlaps for the X and Y axes. 191 | float minOverlapX{ballFromLeft ? overlapLeft : overlapRight}; 192 | float minOverlapY{ballFromTop ? overlapTop : overlapBottom}; 193 | 194 | // If the magnitude of the X overlap is less than the magnitude 195 | // of the Y overlap, we can safely assume the ball hit the brick 196 | // horizontally - otherwise, the ball hit the brick vertically. 197 | 198 | // Then, upon our assumptions, we change either the X or Y velocity 199 | // of the ball, creating a "realistic" response for the collision. 200 | if(std::abs(minOverlapX) < std::abs(minOverlapY)) 201 | { 202 | mBall.velocity.x = 203 | ballFromLeft ? -Ball::defVelocity : Ball::defVelocity; 204 | } 205 | else 206 | { 207 | mBall.velocity.y = ballFromTop ? -Ball::defVelocity : Ball::defVelocity; 208 | } 209 | } 210 | 211 | int main() 212 | { 213 | Ball ball{wndWidth / 2.f, wndHeight / 2.f}; 214 | Paddle paddle{wndWidth / 2, wndHeight - 50}; 215 | std::vector bricks; 216 | 217 | constexpr int brkCountX{11}; 218 | constexpr int brkCountY{4}; 219 | constexpr int brkStartColumn{1}; 220 | constexpr int brkStartRow{2}; 221 | constexpr float brkSpacing{3.f}; 222 | constexpr float brkOffsetX{22.f}; 223 | 224 | for(int iX{0}; iX < brkCountX; ++iX) 225 | for(int iY{0}; iY < brkCountY; ++iY) 226 | { 227 | float x{(iX + brkStartColumn) * (Brick::defWidth + brkSpacing)}; 228 | float y{(iY + brkStartRow) * (Brick::defHeight + brkSpacing)}; 229 | 230 | bricks.emplace_back(brkOffsetX + x, y); 231 | } 232 | 233 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 7"}; 234 | window.setFramerateLimit(60); 235 | 236 | while(true) 237 | { 238 | window.clear(sf::Color::Black); 239 | 240 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 241 | 242 | ball.update(); 243 | paddle.update(); 244 | for(auto& brick : bricks) 245 | { 246 | brick.update(); 247 | 248 | // Let's test collision for every brick. 249 | solveBrickBallCollision(brick, ball); 250 | } 251 | 252 | // After testing the collision, it is possible that some bricks 253 | // are now marked as "destroyed". We need to get rid of all the 254 | // destroyed bricks. 255 | 256 | // We will use the "erase-remove idiom" to remove all destroyed 257 | // bricks from the brick vector - using a generic C++14 lambda. 258 | 259 | // `std::remove_if` re-arranges the elements of a container 260 | // in such a way that elements to be erased are moved towards 261 | // the end of a vector. 262 | 263 | // By calling `std::vector::erase` with the iterator returned 264 | // by `std::remove_if` and the end iterator, we remove all the 265 | // destroyed bricks. 266 | bricks.erase(std::remove_if(std::begin(bricks), std::end(bricks), 267 | [](const auto& mBrick) 268 | { 269 | return mBrick.destroyed; 270 | }), 271 | std::end(bricks)); 272 | 273 | solvePaddleBallCollision(paddle, ball); 274 | 275 | ball.draw(window); 276 | paddle.draw(window); 277 | for(auto& brick : bricks) brick.draw(window); 278 | 279 | window.display(); 280 | } 281 | 282 | return 0; 283 | } -------------------------------------------------------------------------------- /code/p08.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // Before completing our game, let's spend some time refactoring 6 | // the code. There is a lot of unnecessary duplication! 7 | 8 | #include 9 | 10 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 11 | 12 | // First of all, we have trivial code repetition for our "simple 13 | // getters". It is sufficient to create two base classes our objects 14 | // will inherit from: one for rectangles and one for circles. 15 | struct Rectangle 16 | { 17 | sf::RectangleShape shape; 18 | 19 | float x() const noexcept { return shape.getPosition().x; } 20 | float y() const noexcept { return shape.getPosition().y; } 21 | float width() const noexcept { return shape.getSize().x; } 22 | float height() const noexcept { return shape.getSize().y; } 23 | float left() const noexcept { return x() - width() / 2.f; } 24 | float right() const noexcept { return x() + width() / 2.f; } 25 | float top() const noexcept { return y() - height() / 2.f; } 26 | float bottom() const noexcept { return y() + height() / 2.f; } 27 | }; 28 | 29 | struct Circle 30 | { 31 | sf::CircleShape shape; 32 | 33 | float x() const noexcept { return shape.getPosition().x; } 34 | float y() const noexcept { return shape.getPosition().y; } 35 | float radius() const noexcept { return shape.getRadius(); } 36 | float left() const noexcept { return x() - radius(); } 37 | float right() const noexcept { return x() + radius(); } 38 | float top() const noexcept { return y() - radius(); } 39 | float bottom() const noexcept { return y() + radius(); } 40 | }; 41 | 42 | // Let's adapt our classes to the new hierarchy. 43 | 44 | class Ball : public Circle 45 | { 46 | public: 47 | static const sf::Color defColor; 48 | static constexpr float defRadius{10.f}; 49 | static constexpr float defVelocity{8.f}; 50 | 51 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 52 | 53 | Ball(float mX, float mY) 54 | { 55 | shape.setPosition(mX, mY); 56 | shape.setRadius(defRadius); 57 | shape.setFillColor(defColor); 58 | shape.setOrigin(defRadius, defRadius); 59 | } 60 | 61 | void update() 62 | { 63 | shape.move(velocity); 64 | solveBoundCollisions(); 65 | } 66 | 67 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 68 | 69 | private: 70 | void solveBoundCollisions() noexcept 71 | { 72 | if(left() < 0) 73 | velocity.x = defVelocity; 74 | else if(right() > wndWidth) 75 | velocity.x = -defVelocity; 76 | 77 | if(top() < 0) 78 | velocity.y = defVelocity; 79 | else if(bottom() > wndHeight) 80 | velocity.y = -defVelocity; 81 | } 82 | }; 83 | 84 | const sf::Color Ball::defColor{sf::Color::Red}; 85 | 86 | class Paddle : public Rectangle 87 | { 88 | public: 89 | static const sf::Color defColor; 90 | static constexpr float defWidth{60.f}; 91 | static constexpr float defHeight{20.f}; 92 | static constexpr float defVelocity{8.f}; 93 | 94 | sf::Vector2f velocity; 95 | 96 | Paddle(float mX, float mY) 97 | { 98 | shape.setPosition(mX, mY); 99 | shape.setSize({defWidth, defHeight}); 100 | shape.setFillColor(defColor); 101 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 102 | } 103 | 104 | void update() 105 | { 106 | processPlayerInput(); 107 | shape.move(velocity); 108 | } 109 | 110 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 111 | 112 | private: 113 | void processPlayerInput() 114 | { 115 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left) && left() > 0) 116 | velocity.x = -defVelocity; 117 | else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right) && 118 | right() < wndWidth) 119 | velocity.x = defVelocity; 120 | else 121 | velocity.x = 0; 122 | } 123 | }; 124 | 125 | const sf::Color Paddle::defColor{sf::Color::Red}; 126 | 127 | class Brick : public Rectangle 128 | { 129 | public: 130 | static const sf::Color defColor; 131 | static constexpr float defWidth{60.f}; 132 | static constexpr float defHeight{20.f}; 133 | static constexpr float defVelocity{8.f}; 134 | 135 | bool destroyed{false}; 136 | 137 | Brick(float mX, float mY) 138 | { 139 | shape.setPosition(mX, mY); 140 | shape.setSize({defWidth, defHeight}); 141 | shape.setFillColor(defColor); 142 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 143 | } 144 | 145 | void update() {} 146 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 147 | }; 148 | 149 | const sf::Color Brick::defColor{sf::Color::Yellow}; 150 | 151 | template 152 | bool isIntersecting(const T1& mA, const T2& mB) noexcept 153 | { 154 | return mA.right() >= mB.left() && mA.left() <= mB.right() && 155 | mA.bottom() >= mB.top() && mA.top() <= mB.bottom(); 156 | } 157 | 158 | void solvePaddleBallCollision(const Paddle& mPaddle, Ball& mBall) noexcept 159 | { 160 | if(!isIntersecting(mPaddle, mBall)) return; 161 | 162 | mBall.velocity.y = -Ball::defVelocity; 163 | mBall.velocity.x = 164 | mBall.x() < mPaddle.x() ? -Ball::defVelocity : Ball::defVelocity; 165 | } 166 | 167 | void solveBrickBallCollision(Brick& mBrick, Ball& mBall) noexcept 168 | { 169 | if(!isIntersecting(mBrick, mBall)) return; 170 | mBrick.destroyed = true; 171 | 172 | float overlapLeft{mBall.right() - mBrick.left()}; 173 | float overlapRight{mBrick.right() - mBall.left()}; 174 | float overlapTop{mBall.bottom() - mBrick.top()}; 175 | float overlapBottom{mBrick.bottom() - mBall.top()}; 176 | 177 | bool ballFromLeft(std::abs(overlapLeft) < std::abs(overlapRight)); 178 | bool ballFromTop(std::abs(overlapTop) < std::abs(overlapBottom)); 179 | 180 | float minOverlapX{ballFromLeft ? overlapLeft : overlapRight}; 181 | float minOverlapY{ballFromTop ? overlapTop : overlapBottom}; 182 | 183 | if(std::abs(minOverlapX) < std::abs(minOverlapY)) 184 | mBall.velocity.x = 185 | ballFromLeft ? -Ball::defVelocity : Ball::defVelocity; 186 | else 187 | mBall.velocity.y = ballFromTop ? -Ball::defVelocity : Ball::defVelocity; 188 | } 189 | 190 | int main() 191 | { 192 | constexpr int brkCountX{11}, brkCountY{4}; 193 | constexpr int brkStartColumn{1}, brkStartRow{2}; 194 | constexpr float brkSpacing{3.f}, brkOffsetX{22.f}; 195 | 196 | Ball ball{wndWidth / 2.f, wndHeight / 2.f}; 197 | Paddle paddle{wndWidth / 2, wndHeight - 50}; 198 | std::vector bricks; 199 | 200 | for(int iX{0}; iX < brkCountX; ++iX) 201 | for(int iY{0}; iY < brkCountY; ++iY) 202 | { 203 | float x{(iX + brkStartColumn) * (Brick::defWidth + brkSpacing)}; 204 | float y{(iY + brkStartRow) * (Brick::defHeight + brkSpacing)}; 205 | 206 | bricks.emplace_back(brkOffsetX + x, y); 207 | } 208 | 209 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 8"}; 210 | window.setFramerateLimit(60); 211 | 212 | while(true) 213 | { 214 | window.clear(sf::Color::Black); 215 | 216 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 217 | 218 | ball.update(); 219 | paddle.update(); 220 | for(auto& brick : bricks) 221 | { 222 | brick.update(); 223 | solveBrickBallCollision(brick, ball); 224 | } 225 | 226 | bricks.erase(std::remove_if(std::begin(bricks), std::end(bricks), 227 | [](const auto& mBrick) 228 | { 229 | return mBrick.destroyed; 230 | }), 231 | std::end(bricks)); 232 | 233 | solvePaddleBallCollision(paddle, ball); 234 | 235 | ball.draw(window); 236 | paddle.draw(window); 237 | for(auto& brick : bricks) brick.draw(window); 238 | 239 | window.display(); 240 | } 241 | 242 | return 0; 243 | } 244 | 245 | // By ignoring the comments and slightly altering the formatting, 246 | // we have actually reached our goal: we created a playable arkanoid 247 | // clone from scratch, in under 200 lines of code. 248 | 249 | // We can still highly improve the code architecture, though. 250 | // A good idea to improve flexibility and extensibility of the system 251 | // would be creating a base polymorphic `Entity` class that our 252 | // game objects inherit from. Also a `Game` class that handles 253 | // window management and win/lose conditions, and a `Manager` class 254 | // that will help us create/destroy/store entities. 255 | // {Info: class hierarchy} 256 | // {Info: game architecture} -------------------------------------------------------------------------------- /code/p09.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // In this code segment we'll start improving the game's code 6 | // architecture. The first step will be creating a `Game` class 7 | // that will encapsulate the game's state. 8 | 9 | #include 10 | 11 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 12 | 13 | struct Rectangle 14 | { 15 | sf::RectangleShape shape; 16 | 17 | float x() const noexcept { return shape.getPosition().x; } 18 | float y() const noexcept { return shape.getPosition().y; } 19 | float width() const noexcept { return shape.getSize().x; } 20 | float height() const noexcept { return shape.getSize().y; } 21 | float left() const noexcept { return x() - width() / 2.f; } 22 | float right() const noexcept { return x() + width() / 2.f; } 23 | float top() const noexcept { return y() - height() / 2.f; } 24 | float bottom() const noexcept { return y() + height() / 2.f; } 25 | }; 26 | 27 | struct Circle 28 | { 29 | sf::CircleShape shape; 30 | 31 | float x() const noexcept { return shape.getPosition().x; } 32 | float y() const noexcept { return shape.getPosition().y; } 33 | float radius() const noexcept { return shape.getRadius(); } 34 | float left() const noexcept { return x() - radius(); } 35 | float right() const noexcept { return x() + radius(); } 36 | float top() const noexcept { return y() - radius(); } 37 | float bottom() const noexcept { return y() + radius(); } 38 | }; 39 | 40 | class Ball : public Circle 41 | { 42 | public: 43 | static const sf::Color defColor; 44 | static constexpr float defRadius{10.f}; 45 | static constexpr float defVelocity{8.f}; 46 | 47 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 48 | 49 | Ball(float mX, float mY) 50 | { 51 | shape.setPosition(mX, mY); 52 | shape.setRadius(defRadius); 53 | shape.setFillColor(defColor); 54 | shape.setOrigin(defRadius, defRadius); 55 | } 56 | 57 | void update() 58 | { 59 | shape.move(velocity); 60 | solveBoundCollisions(); 61 | } 62 | 63 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 64 | 65 | private: 66 | void solveBoundCollisions() noexcept 67 | { 68 | if(left() < 0) 69 | velocity.x = defVelocity; 70 | else if(right() > wndWidth) 71 | velocity.x = -defVelocity; 72 | 73 | if(top() < 0) 74 | velocity.y = defVelocity; 75 | else if(bottom() > wndHeight) 76 | velocity.y = -defVelocity; 77 | } 78 | }; 79 | 80 | const sf::Color Ball::defColor{sf::Color::Red}; 81 | 82 | class Paddle : public Rectangle 83 | { 84 | public: 85 | static const sf::Color defColor; 86 | static constexpr float defWidth{60.f}; 87 | static constexpr float defHeight{20.f}; 88 | static constexpr float defVelocity{8.f}; 89 | 90 | sf::Vector2f velocity; 91 | 92 | Paddle(float mX, float mY) 93 | { 94 | shape.setPosition(mX, mY); 95 | shape.setSize({defWidth, defHeight}); 96 | shape.setFillColor(defColor); 97 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 98 | } 99 | 100 | void update() 101 | { 102 | processPlayerInput(); 103 | shape.move(velocity); 104 | } 105 | 106 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 107 | 108 | private: 109 | void processPlayerInput() 110 | { 111 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left) && left() > 0) 112 | velocity.x = -defVelocity; 113 | else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right) && 114 | right() < wndWidth) 115 | velocity.x = defVelocity; 116 | else 117 | velocity.x = 0; 118 | } 119 | }; 120 | 121 | const sf::Color Paddle::defColor{sf::Color::Red}; 122 | 123 | class Brick : public Rectangle 124 | { 125 | public: 126 | static const sf::Color defColor; 127 | static constexpr float defWidth{60.f}; 128 | static constexpr float defHeight{20.f}; 129 | static constexpr float defVelocity{8.f}; 130 | 131 | bool destroyed{false}; 132 | 133 | Brick(float mX, float mY) 134 | { 135 | shape.setPosition(mX, mY); 136 | shape.setSize({defWidth, defHeight}); 137 | shape.setFillColor(defColor); 138 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 139 | } 140 | 141 | void update() {} 142 | void draw(sf::RenderWindow& mTarget) { mTarget.draw(shape); } 143 | }; 144 | 145 | const sf::Color Brick::defColor{sf::Color::Yellow}; 146 | 147 | template 148 | bool isIntersecting(const T1& mA, const T2& mB) noexcept 149 | { 150 | return mA.right() >= mB.left() && mA.left() <= mB.right() && 151 | mA.bottom() >= mB.top() && mA.top() <= mB.bottom(); 152 | } 153 | 154 | void solvePaddleBallCollision(const Paddle& mPaddle, Ball& mBall) noexcept 155 | { 156 | if(!isIntersecting(mPaddle, mBall)) return; 157 | 158 | mBall.velocity.y = -Ball::defVelocity; 159 | mBall.velocity.x = 160 | mBall.x() < mPaddle.x() ? -Ball::defVelocity : Ball::defVelocity; 161 | } 162 | 163 | void solveBrickBallCollision(Brick& mBrick, Ball& mBall) noexcept 164 | { 165 | if(!isIntersecting(mBrick, mBall)) return; 166 | mBrick.destroyed = true; 167 | 168 | float overlapLeft{mBall.right() - mBrick.left()}; 169 | float overlapRight{mBrick.right() - mBall.left()}; 170 | float overlapTop{mBall.bottom() - mBrick.top()}; 171 | float overlapBottom{mBrick.bottom() - mBall.top()}; 172 | 173 | bool ballFromLeft(std::abs(overlapLeft) < std::abs(overlapRight)); 174 | bool ballFromTop(std::abs(overlapTop) < std::abs(overlapBottom)); 175 | 176 | float minOverlapX{ballFromLeft ? overlapLeft : overlapRight}; 177 | float minOverlapY{ballFromTop ? overlapTop : overlapBottom}; 178 | 179 | if(std::abs(minOverlapX) < std::abs(minOverlapY)) 180 | mBall.velocity.x = 181 | ballFromLeft ? -Ball::defVelocity : Ball::defVelocity; 182 | else 183 | mBall.velocity.y = ballFromTop ? -Ball::defVelocity : Ball::defVelocity; 184 | } 185 | 186 | // The `Game` class will store game constants and elements, and 187 | // will keep track of the game's state. Also, it will provide 188 | // functionality to pause and restart the game. 189 | class Game 190 | { 191 | private: 192 | // Let's create an enum for the possible game states. 193 | enum class State 194 | { 195 | Paused, 196 | InProgress 197 | }; 198 | 199 | static constexpr int brkCountX{11}, brkCountY{4}; 200 | static constexpr int brkStartColumn{1}, brkStartRow{2}; 201 | static constexpr float brkSpacing{3.f}, brkOffsetX{22.f}; 202 | 203 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 9"}; 204 | 205 | Ball ball{wndWidth / 2.f, wndHeight / 2.f}; 206 | Paddle paddle{wndWidth / 2, wndHeight - 50}; 207 | std::vector bricks; 208 | 209 | // Let's add some fields to keep track of the game status. 210 | State state{State::InProgress}; 211 | bool pausePressedLastFrame{false}; 212 | 213 | public: 214 | Game() { window.setFramerateLimit(60); } 215 | 216 | // The `restart` method will re-create all game objects and restore 217 | // the game to a default state. 218 | void restart() 219 | { 220 | state = State::Paused; 221 | 222 | for(int iX{0}; iX < brkCountX; ++iX) 223 | for(int iY{0}; iY < brkCountY; ++iY) 224 | { 225 | float x{(iX + brkStartColumn) * (Brick::defWidth + brkSpacing)}; 226 | float y{(iY + brkStartRow) * (Brick::defHeight + brkSpacing)}; 227 | 228 | bricks.emplace_back(brkOffsetX + x, y); 229 | } 230 | 231 | ball = Ball{wndWidth / 2.f, wndHeight / 2.f}; 232 | paddle = Paddle{wndWidth / 2, wndHeight - 50}; 233 | } 234 | 235 | // The `run` method will start the game loop. 236 | void run() 237 | { 238 | while(true) 239 | { 240 | window.clear(sf::Color::Black); 241 | 242 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 243 | 244 | // The `P` key will toggle the pause. To prevent continuous 245 | // use of the pause button, we need to check if the input 246 | // was pressed last frame. 247 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::P)) 248 | { 249 | // If `P` was not pressed last frame, we can toggle 250 | // the state. 251 | if(!pausePressedLastFrame) 252 | { 253 | if(state == State::Paused) 254 | state = State::InProgress; 255 | else if(state == State::InProgress) 256 | state = State::Paused; 257 | } 258 | 259 | pausePressedLastFrame = true; 260 | } 261 | else 262 | pausePressedLastFrame = false; 263 | 264 | // Let's also use the `R` key to restart the game. 265 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::R)) restart(); 266 | 267 | // If the game is paused, we'll only draw game elements, 268 | // without updating them. 269 | if(state != State::Paused) 270 | { 271 | // The rest of the game loop code is exactly the same. 272 | 273 | ball.update(); 274 | paddle.update(); 275 | for(auto& brick : bricks) 276 | { 277 | brick.update(); 278 | solveBrickBallCollision(brick, ball); 279 | } 280 | 281 | bricks.erase( 282 | std::remove_if(std::begin(bricks), std::end(bricks), 283 | [](const auto& mBrick) 284 | { 285 | return mBrick.destroyed; 286 | }), 287 | std::end(bricks)); 288 | 289 | solvePaddleBallCollision(paddle, ball); 290 | } 291 | 292 | ball.draw(window); 293 | paddle.draw(window); 294 | for(auto& brick : bricks) brick.draw(window); 295 | 296 | window.display(); 297 | } 298 | } 299 | }; 300 | 301 | int main() 302 | { 303 | // To start our game, we create a `Game` instance, 304 | // restart it and run it. 305 | Game game; 306 | game.restart(); 307 | game.run(); 308 | return 0; 309 | } -------------------------------------------------------------------------------- /code/p10.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // The next step that will greatly improve our code architecture 6 | // is creating a system that allows us to quickly create new 7 | // game object types during development, and also instantiate and 8 | // destroy game object instances at run-time. We're gonna need 9 | // a polymorphic hierarchy for our objects, and a manager class 10 | // that will help us deal with them. 11 | 12 | // Let's include the `` header, as we're going to use 13 | // smart pointers. 14 | #include 15 | 16 | // We will also need the `` header and the `` header 17 | // to query entities by type. 18 | #include 19 | #include 20 | #include 21 | 22 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 23 | 24 | // All game objects share the same interface. 25 | // They provide a `update` and a `draw` method. 26 | // Let's create a polymorphic hierarchy for our objects. 27 | // The base class will be called `Entity`. 28 | // It will also store the `destroyed` bool value, so that 29 | // we can "mark" dead entities whose memory will be reclaimed. 30 | class Entity 31 | { 32 | public: 33 | bool destroyed{false}; 34 | 35 | // We use the `virtual` keyword to enable polymorphism. 36 | virtual ~Entity() {} 37 | virtual void update() {} 38 | virtual void draw(sf::RenderWindow& mTarget) {} 39 | }; 40 | 41 | // Now, we need a manager class that will help us create, 42 | // destroy, update and query entities. 43 | class Manager 44 | { 45 | private: 46 | // Since our entities are now polymorphic, we need to store 47 | // them on the heap. We'll use a `std::vector` of 48 | // `std::unique_ptr` to enable polymorphism. 49 | std::vector> entities; 50 | 51 | // We also need to get all entities of a certain type 52 | // during the game loop. For example, we need to check 53 | // if any ball collides with any brick. Instead of manually 54 | // checking the type of the entity during the loop, we can 55 | // store a "database" of game objects, an `std::map` of 56 | // `std::vector` instances where the key is a `typeid` hash. 57 | std::map> groupedEntities; 58 | 59 | public: 60 | // To properly populate/query these data structures, we'll need 61 | // some methods. We're gonna use C++11 variadic templates to 62 | // allow the user to create entities with any constructor signature. 63 | 64 | // The first method we're gonna define is `create`: it will 65 | // take a game object type `T`, and some `TArgs` constructor 66 | // arguments types as template parameters, and return a reference 67 | // to an heap-allocated object. The object itself will be stored 68 | // as an `std::unique_ptr` in the `entities` vector, and a pointer 69 | // to it will be stored in `groupedEntities` for easy querying. 70 | // The key that will be used for the `groupedEntities` storage 71 | // will be the type hash of `T` retrieved thanks to ``. 72 | template 73 | T& create(TArgs&&... mArgs) 74 | { 75 | // Let's make sure, using a `static_assert`, that the type `T` 76 | // is a child of the `Entity` type inheritance. 77 | static_assert(std::is_base_of::value, 78 | "`T` must be derived from `Entity`"); 79 | 80 | // Let's create the object itself, using `std::make_unique`. 81 | // We'll use perfect forwarding to make sure the types of the 82 | // arguments passed to `T`'s constructor will be forwarded 83 | // properly. 84 | auto uPtr(std::make_unique(std::forward(mArgs)...)); 85 | auto ptr(uPtr.get()); 86 | 87 | // Let's retrieve the `T` type hash with the `typeid` keyword. 88 | // The retrieved hash code is guaranteed to be the same for `T`. 89 | // Let's use it as the key for the `groupedEntities` entry. 90 | groupedEntities[typeid(T).hash_code()].emplace_back(ptr); 91 | 92 | // [07/10/2014 addendum]: `hash_code()` does not actually guarantee 93 | // that the codes generated for two different types will be 94 | // unique. Learn more about this issue and a possible solution on 95 | // cppreference: 96 | // http://en.cppreference.com/w/cpp/types/type_info/hash_code 97 | 98 | // Now let's move the `std::unique_ptr` in the `entities` 99 | // vector. 100 | entities.emplace_back(std::move(uPtr)); 101 | 102 | return *ptr; 103 | } 104 | 105 | // Removal of an entity will work in a different way: instead of 106 | // directly removing the entity from the storage, we will simply 107 | // mark it as "destroyed". Another method, called `refresh`, will 108 | // take care of cleaning up all the "destroyed" entities, at the 109 | // end of an update. This has major performance advantages, and 110 | // also allows us to correctly access a soon-to-be-destroyed entity 111 | // without accessing corrupted memory. 112 | void refresh() 113 | { 114 | // This method will take care of cleaning up the destroyed 115 | // entities. We begin looking for entities to remove in the 116 | // `groupedEntities` storage, so that their content will 117 | // still be accessible. 118 | for(auto& pair : groupedEntities) 119 | { 120 | auto& vector(pair.second); 121 | 122 | vector.erase(std::remove_if(std::begin(vector), std::end(vector), 123 | [](auto mPtr) 124 | { 125 | return mPtr->destroyed; 126 | }), 127 | std::end(vector)); 128 | } 129 | 130 | // After that, we use the same idiom on the `entities` vector. 131 | // Since `entities` stores smart pointers, the memory will be 132 | // automatically freed when they are removed from the vector. 133 | entities.erase(std::remove_if(std::begin(entities), std::end(entities), 134 | [](const auto& mUPtr) 135 | { 136 | return mUPtr->destroyed; 137 | }), 138 | std::end(entities)); 139 | } 140 | 141 | // We'll also need a `clear` method to destroy all entities. 142 | void clear() 143 | { 144 | groupedEntities.clear(); 145 | entities.clear(); 146 | } 147 | 148 | // And a template method to query the grouped storage. 149 | // A good candidate for C++14's automatic function 150 | // return type deduction. 151 | template 152 | auto& getAll() 153 | { 154 | return groupedEntities[typeid(T).hash_code()]; 155 | } 156 | 157 | // Another useful method will allow the user to execute arbitrary 158 | // code on all entities of a certain type. 159 | template 160 | void forEach(const TFunc& mFunc) 161 | { 162 | // Retrieve all entities of type `T`. 163 | auto& vector(getAll()); 164 | 165 | // For each pointer in the entity vector, simply cast the 166 | // pointer to its "real" type then call the function with the 167 | // casted pointer, dereferenced. 168 | for(auto ptr : vector) mFunc(*reinterpret_cast(ptr)); 169 | } 170 | 171 | // Lastly, we'll implement a method to update all entities, and a 172 | // method to draw all entities. 173 | 174 | void update() 175 | { 176 | for(auto& e : entities) e->update(); 177 | } 178 | void draw(sf::RenderWindow& mTarget) 179 | { 180 | for(auto& e : entities) e->draw(mTarget); 181 | } 182 | }; 183 | 184 | struct Rectangle 185 | { 186 | sf::RectangleShape shape; 187 | 188 | float x() const noexcept { return shape.getPosition().x; } 189 | float y() const noexcept { return shape.getPosition().y; } 190 | float width() const noexcept { return shape.getSize().x; } 191 | float height() const noexcept { return shape.getSize().y; } 192 | float left() const noexcept { return x() - width() / 2.f; } 193 | float right() const noexcept { return x() + width() / 2.f; } 194 | float top() const noexcept { return y() - height() / 2.f; } 195 | float bottom() const noexcept { return y() + height() / 2.f; } 196 | }; 197 | 198 | struct Circle 199 | { 200 | sf::CircleShape shape; 201 | 202 | float x() const noexcept { return shape.getPosition().x; } 203 | float y() const noexcept { return shape.getPosition().y; } 204 | float radius() const noexcept { return shape.getRadius(); } 205 | float left() const noexcept { return x() - radius(); } 206 | float right() const noexcept { return x() + radius(); } 207 | float top() const noexcept { return y() - radius(); } 208 | float bottom() const noexcept { return y() + radius(); } 209 | }; 210 | 211 | // Let's now adapt our classes to the new architecture. 212 | 213 | class Ball : public Entity, public Circle 214 | { 215 | public: 216 | static const sf::Color defColor; 217 | static constexpr float defRadius{10.f}, defVelocity{8.f}; 218 | 219 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 220 | 221 | Ball(float mX, float mY) 222 | { 223 | shape.setPosition(mX, mY); 224 | shape.setRadius(defRadius); 225 | shape.setFillColor(defColor); 226 | shape.setOrigin(defRadius, defRadius); 227 | } 228 | 229 | // The `override` C++11 keyword is incredibly useful. 230 | // It makes sure that you're overriding a virtual method 231 | // of the base class. 232 | void update() override 233 | { 234 | shape.move(velocity); 235 | solveBoundCollisions(); 236 | } 237 | 238 | void draw(sf::RenderWindow& mTarget) override { mTarget.draw(shape); } 239 | 240 | private: 241 | void solveBoundCollisions() noexcept 242 | { 243 | if(left() < 0) 244 | velocity.x = defVelocity; 245 | else if(right() > wndWidth) 246 | velocity.x = -defVelocity; 247 | 248 | if(top() < 0) 249 | velocity.y = defVelocity; 250 | else if(bottom() > wndHeight) 251 | velocity.y = -defVelocity; 252 | } 253 | }; 254 | 255 | const sf::Color Ball::defColor{sf::Color::Red}; 256 | 257 | class Paddle : public Entity, public Rectangle 258 | { 259 | public: 260 | static const sf::Color defColor; 261 | static constexpr float defWidth{60.f}, defHeight{20.f}; 262 | static constexpr float defVelocity{8.f}; 263 | 264 | sf::Vector2f velocity; 265 | 266 | Paddle(float mX, float mY) 267 | { 268 | shape.setPosition(mX, mY); 269 | shape.setSize({defWidth, defHeight}); 270 | shape.setFillColor(defColor); 271 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 272 | } 273 | 274 | void update() override 275 | { 276 | processPlayerInput(); 277 | shape.move(velocity); 278 | } 279 | 280 | void draw(sf::RenderWindow& mTarget) override { mTarget.draw(shape); } 281 | 282 | private: 283 | void processPlayerInput() 284 | { 285 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left) && left() > 0) 286 | velocity.x = -defVelocity; 287 | else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right) && 288 | right() < wndWidth) 289 | velocity.x = defVelocity; 290 | else 291 | velocity.x = 0; 292 | } 293 | }; 294 | 295 | const sf::Color Paddle::defColor{sf::Color::Red}; 296 | 297 | class Brick : public Entity, public Rectangle 298 | { 299 | public: 300 | static const sf::Color defColor; 301 | static constexpr float defWidth{60.f}, defHeight{20.f}; 302 | static constexpr float defVelocity{8.f}; 303 | 304 | Brick(float mX, float mY) 305 | { 306 | shape.setPosition(mX, mY); 307 | shape.setSize({defWidth, defHeight}); 308 | shape.setFillColor(defColor); 309 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 310 | } 311 | 312 | void draw(sf::RenderWindow& mTarget) override { mTarget.draw(shape); } 313 | }; 314 | 315 | const sf::Color Brick::defColor{sf::Color::Yellow}; 316 | 317 | template 318 | bool isIntersecting(const T1& mA, const T2& mB) noexcept 319 | { 320 | return mA.right() >= mB.left() && mA.left() <= mB.right() && 321 | mA.bottom() >= mB.top() && mA.top() <= mB.bottom(); 322 | } 323 | 324 | void solvePaddleBallCollision(const Paddle& mPaddle, Ball& mBall) noexcept 325 | { 326 | if(!isIntersecting(mPaddle, mBall)) return; 327 | 328 | mBall.velocity.y = -Ball::defVelocity; 329 | mBall.velocity.x = 330 | mBall.x() < mPaddle.x() ? -Ball::defVelocity : Ball::defVelocity; 331 | } 332 | 333 | void solveBrickBallCollision(Brick& mBrick, Ball& mBall) noexcept 334 | { 335 | if(!isIntersecting(mBrick, mBall)) return; 336 | mBrick.destroyed = true; 337 | 338 | float overlapLeft{mBall.right() - mBrick.left()}; 339 | float overlapRight{mBrick.right() - mBall.left()}; 340 | float overlapTop{mBall.bottom() - mBrick.top()}; 341 | float overlapBottom{mBrick.bottom() - mBall.top()}; 342 | 343 | bool ballFromLeft(std::abs(overlapLeft) < std::abs(overlapRight)); 344 | bool ballFromTop(std::abs(overlapTop) < std::abs(overlapBottom)); 345 | 346 | float minOverlapX{ballFromLeft ? overlapLeft : overlapRight}; 347 | float minOverlapY{ballFromTop ? overlapTop : overlapBottom}; 348 | 349 | if(std::abs(minOverlapX) < std::abs(minOverlapY)) 350 | mBall.velocity.x = 351 | ballFromLeft ? -Ball::defVelocity : Ball::defVelocity; 352 | else 353 | mBall.velocity.y = ballFromTop ? -Ball::defVelocity : Ball::defVelocity; 354 | } 355 | 356 | class Game 357 | { 358 | private: 359 | enum class State 360 | { 361 | Paused, 362 | InProgress 363 | }; 364 | 365 | static constexpr int brkCountX{11}, brkCountY{4}; 366 | static constexpr int brkStartColumn{1}, brkStartRow{2}; 367 | static constexpr float brkSpacing{3.f}, brkOffsetX{22.f}; 368 | 369 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 10"}; 370 | 371 | // The `Game` class will now store our manager. 372 | Manager manager; 373 | 374 | State state{State::InProgress}; 375 | bool pausePressedLastFrame{false}; 376 | 377 | public: 378 | Game() { window.setFramerateLimit(60); } 379 | 380 | void restart() 381 | { 382 | // Restarting will clear the manager and re-create all entities. 383 | state = State::Paused; 384 | manager.clear(); 385 | 386 | for(int iX{0}; iX < brkCountX; ++iX) 387 | for(int iY{0}; iY < brkCountY; ++iY) 388 | { 389 | float x{(iX + brkStartColumn) * (Brick::defWidth + brkSpacing)}; 390 | float y{(iY + brkStartRow) * (Brick::defHeight + brkSpacing)}; 391 | 392 | // As you can see, creating entities using the manager is 393 | // really straightforward. 394 | manager.create(brkOffsetX + x, y); 395 | } 396 | 397 | manager.create(wndWidth / 2.f, wndHeight / 2.f); 398 | manager.create(wndWidth / 2, wndHeight - 50); 399 | } 400 | 401 | void run() 402 | { 403 | while(true) 404 | { 405 | window.clear(sf::Color::Black); 406 | 407 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 408 | 409 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::P)) 410 | { 411 | if(!pausePressedLastFrame) 412 | { 413 | if(state == State::Paused) 414 | state = State::InProgress; 415 | else if(state == State::InProgress) 416 | state = State::Paused; 417 | } 418 | pausePressedLastFrame = true; 419 | } 420 | else 421 | pausePressedLastFrame = false; 422 | 423 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::R)) restart(); 424 | 425 | if(state != State::Paused) 426 | { 427 | // Instead of manually updating every entity, we just 428 | // ask the manager to do the work for us. 429 | manager.update(); 430 | 431 | // The game logic is now much more generic: we ask the 432 | // manager to give us all instances of a certain game 433 | // object type, then we run the collision functions. 434 | // This is very flexible as we can have any number 435 | // of balls and bricks, and adding new types of game 436 | // objects is extremely easy. 437 | manager.forEach([this](auto& mBall) 438 | { 439 | manager.forEach([this, &mBall](auto& mBrick) 440 | { 441 | solveBrickBallCollision(mBrick, mBall); 442 | }); 443 | manager.forEach([this, &mBall](auto& mPaddle) 444 | { 445 | solvePaddleBallCollision(mPaddle, mBall); 446 | }); 447 | }); 448 | 449 | // Now we ask the manager to clean-up the destroyed 450 | // entities. 451 | manager.refresh(); 452 | } 453 | 454 | manager.draw(window); 455 | window.display(); 456 | } 457 | } 458 | }; 459 | 460 | 461 | int main() 462 | { 463 | Game game; 464 | game.restart(); 465 | game.run(); 466 | return 0; 467 | } 468 | 469 | // The code is now much more complex, but the advantages the programmer 470 | // gets from this kind of design are significant. 471 | // Using a manager to deal with entities greatly simplifies the addition 472 | // of new game object types, and also allows the developer to pay less 473 | // attention to memory-management. 474 | 475 | // In the next (and last) code segment, we'll add some completely optional 476 | // finishing touches to our simple arkanoid clone: 477 | // * Text 478 | // * Win/lose states (limited lives) 479 | // * Multi-hit bricks -------------------------------------------------------------------------------- /code/p11.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Vittorio Romeo 2 | // License: MIT License | http://opensource.org/licenses/MIT 3 | // http://vittorioromeo.info | vittorio.romeo@outlook.com 4 | 5 | // In this last code segment, we'll add some features to our game 6 | // and finish it: 7 | // * Text 8 | // * Win/lose states (limited lives) 9 | // * Multi-hit bricks 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | constexpr unsigned int wndWidth{800}, wndHeight{600}; 17 | 18 | class Entity 19 | { 20 | public: 21 | bool destroyed{false}; 22 | 23 | virtual ~Entity() {} 24 | virtual void update() {} 25 | virtual void draw(sf::RenderWindow& mTarget) {} 26 | }; 27 | 28 | class Manager 29 | { 30 | private: 31 | std::vector> entities; 32 | std::map> groupedEntities; 33 | 34 | public: 35 | template 36 | T& create(TArgs&&... mArgs) 37 | { 38 | static_assert(std::is_base_of::value, 39 | "`T` must be derived from `Entity`"); 40 | 41 | auto uPtr(std::make_unique(std::forward(mArgs)...)); 42 | auto ptr(uPtr.get()); 43 | groupedEntities[typeid(T).hash_code()].emplace_back(ptr); 44 | entities.emplace_back(std::move(uPtr)); 45 | 46 | return *ptr; 47 | } 48 | 49 | void refresh() 50 | { 51 | for(auto& pair : groupedEntities) 52 | { 53 | auto& vector(pair.second); 54 | 55 | vector.erase(std::remove_if(std::begin(vector), std::end(vector), 56 | [](auto mPtr) 57 | { 58 | return mPtr->destroyed; 59 | }), 60 | std::end(vector)); 61 | } 62 | 63 | entities.erase(std::remove_if(std::begin(entities), std::end(entities), 64 | [](const auto& mUPtr) 65 | { 66 | return mUPtr->destroyed; 67 | }), 68 | std::end(entities)); 69 | } 70 | 71 | void clear() 72 | { 73 | groupedEntities.clear(); 74 | entities.clear(); 75 | } 76 | 77 | template 78 | auto& getAll() 79 | { 80 | return groupedEntities[typeid(T).hash_code()]; 81 | } 82 | 83 | template 84 | void forEach(const TFunc& mFunc) 85 | { 86 | for(auto ptr : getAll()) mFunc(*reinterpret_cast(ptr)); 87 | } 88 | 89 | void update() 90 | { 91 | for(auto& e : entities) e->update(); 92 | } 93 | void draw(sf::RenderWindow& mTarget) 94 | { 95 | for(auto& e : entities) e->draw(mTarget); 96 | } 97 | }; 98 | 99 | struct Rectangle 100 | { 101 | sf::RectangleShape shape; 102 | 103 | float x() const noexcept { return shape.getPosition().x; } 104 | float y() const noexcept { return shape.getPosition().y; } 105 | float width() const noexcept { return shape.getSize().x; } 106 | float height() const noexcept { return shape.getSize().y; } 107 | float left() const noexcept { return x() - width() / 2.f; } 108 | float right() const noexcept { return x() + width() / 2.f; } 109 | float top() const noexcept { return y() - height() / 2.f; } 110 | float bottom() const noexcept { return y() + height() / 2.f; } 111 | }; 112 | 113 | struct Circle 114 | { 115 | sf::CircleShape shape; 116 | 117 | float x() const noexcept { return shape.getPosition().x; } 118 | float y() const noexcept { return shape.getPosition().y; } 119 | float radius() const noexcept { return shape.getRadius(); } 120 | float left() const noexcept { return x() - radius(); } 121 | float right() const noexcept { return x() + radius(); } 122 | float top() const noexcept { return y() - radius(); } 123 | float bottom() const noexcept { return y() + radius(); } 124 | }; 125 | 126 | class Ball : public Entity, public Circle 127 | { 128 | public: 129 | static const sf::Color defColor; 130 | static constexpr float defRadius{10.f}, defVelocity{8.f}; 131 | 132 | sf::Vector2f velocity{-defVelocity, -defVelocity}; 133 | 134 | Ball(float mX, float mY) 135 | { 136 | shape.setPosition(mX, mY); 137 | shape.setRadius(defRadius); 138 | shape.setFillColor(defColor); 139 | shape.setOrigin(defRadius, defRadius); 140 | } 141 | 142 | void update() override 143 | { 144 | shape.move(velocity); 145 | solveBoundCollisions(); 146 | } 147 | 148 | void draw(sf::RenderWindow& mTarget) override { mTarget.draw(shape); } 149 | 150 | private: 151 | void solveBoundCollisions() noexcept 152 | { 153 | if(left() < 0) 154 | velocity.x = defVelocity; 155 | else if(right() > wndWidth) 156 | velocity.x = -defVelocity; 157 | 158 | if(top() < 0) 159 | velocity.y = defVelocity; 160 | else if(bottom() > wndHeight) 161 | { 162 | // If the ball leaves the window towards the bottom, 163 | // we destroy it. 164 | destroyed = true; 165 | } 166 | } 167 | }; 168 | 169 | const sf::Color Ball::defColor{sf::Color::Red}; 170 | 171 | class Paddle : public Entity, public Rectangle 172 | { 173 | public: 174 | static const sf::Color defColor; 175 | static constexpr float defWidth{60.f}, defHeight{20.f}; 176 | static constexpr float defVelocity{8.f}; 177 | 178 | sf::Vector2f velocity; 179 | 180 | Paddle(float mX, float mY) 181 | { 182 | shape.setPosition(mX, mY); 183 | shape.setSize({defWidth, defHeight}); 184 | shape.setFillColor(defColor); 185 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 186 | } 187 | 188 | void update() override 189 | { 190 | processPlayerInput(); 191 | shape.move(velocity); 192 | } 193 | 194 | void draw(sf::RenderWindow& mTarget) override { mTarget.draw(shape); } 195 | 196 | private: 197 | void processPlayerInput() 198 | { 199 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left) && left() > 0) 200 | velocity.x = -defVelocity; 201 | else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right) && 202 | right() < wndWidth) 203 | velocity.x = defVelocity; 204 | else 205 | velocity.x = 0; 206 | } 207 | }; 208 | 209 | const sf::Color Paddle::defColor{sf::Color::Red}; 210 | 211 | class Brick : public Entity, public Rectangle 212 | { 213 | public: 214 | static const sf::Color defColorHits1; 215 | static const sf::Color defColorHits2; 216 | static const sf::Color defColorHits3; 217 | static constexpr float defWidth{60.f}, defHeight{20.f}; 218 | static constexpr float defVelocity{8.f}; 219 | 220 | // Let's add a field for the required hits. 221 | int requiredHits{1}; 222 | 223 | Brick(float mX, float mY) 224 | { 225 | shape.setPosition(mX, mY); 226 | shape.setSize({defWidth, defHeight}); 227 | shape.setOrigin(defWidth / 2.f, defHeight / 2.f); 228 | } 229 | 230 | void update() override 231 | { 232 | // Let's alter the color of the brick depending on the 233 | // required hits. 234 | if(requiredHits == 1) 235 | shape.setFillColor(defColorHits1); 236 | else if(requiredHits == 2) 237 | shape.setFillColor(defColorHits2); 238 | else 239 | shape.setFillColor(defColorHits3); 240 | } 241 | void draw(sf::RenderWindow& mTarget) override { mTarget.draw(shape); } 242 | }; 243 | 244 | const sf::Color Brick::defColorHits1{255, 255, 0, 80}; 245 | const sf::Color Brick::defColorHits2{255, 255, 0, 170}; 246 | const sf::Color Brick::defColorHits3{255, 255, 0, 255}; 247 | 248 | template 249 | bool isIntersecting(const T1& mA, const T2& mB) noexcept 250 | { 251 | return mA.right() >= mB.left() && mA.left() <= mB.right() && 252 | mA.bottom() >= mB.top() && mA.top() <= mB.bottom(); 253 | } 254 | 255 | void solvePaddleBallCollision(const Paddle& mPaddle, Ball& mBall) noexcept 256 | { 257 | if(!isIntersecting(mPaddle, mBall)) return; 258 | 259 | mBall.velocity.y = -Ball::defVelocity; 260 | mBall.velocity.x = 261 | mBall.x() < mPaddle.x() ? -Ball::defVelocity : Ball::defVelocity; 262 | } 263 | 264 | void solveBrickBallCollision(Brick& mBrick, Ball& mBall) noexcept 265 | { 266 | if(!isIntersecting(mBrick, mBall)) return; 267 | 268 | // Instead of immediately destroying the brick upon collision, 269 | // we decrease and check its required hits first. 270 | --mBrick.requiredHits; 271 | if(mBrick.requiredHits <= 0) mBrick.destroyed = true; 272 | 273 | float overlapLeft{mBall.right() - mBrick.left()}; 274 | float overlapRight{mBrick.right() - mBall.left()}; 275 | float overlapTop{mBall.bottom() - mBrick.top()}; 276 | float overlapBottom{mBrick.bottom() - mBall.top()}; 277 | 278 | bool ballFromLeft(std::abs(overlapLeft) < std::abs(overlapRight)); 279 | bool ballFromTop(std::abs(overlapTop) < std::abs(overlapBottom)); 280 | 281 | float minOverlapX{ballFromLeft ? overlapLeft : overlapRight}; 282 | float minOverlapY{ballFromTop ? overlapTop : overlapBottom}; 283 | 284 | if(std::abs(minOverlapX) < std::abs(minOverlapY)) 285 | mBall.velocity.x = 286 | ballFromLeft ? -Ball::defVelocity : Ball::defVelocity; 287 | else 288 | mBall.velocity.y = ballFromTop ? -Ball::defVelocity : Ball::defVelocity; 289 | } 290 | 291 | class Game 292 | { 293 | private: 294 | // There now are two additional game states: `GameOver` 295 | // and `Victory`. 296 | enum class State 297 | { 298 | Paused, 299 | GameOver, 300 | InProgress, 301 | Victory 302 | }; 303 | 304 | static constexpr int brkCountX{11}, brkCountY{4}; 305 | static constexpr int brkStartColumn{1}, brkStartRow{2}; 306 | static constexpr float brkSpacing{3.f}, brkOffsetX{22.f}; 307 | 308 | sf::RenderWindow window{{wndWidth, wndHeight}, "Arkanoid - 11"}; 309 | Manager manager; 310 | 311 | // SFML offers an easy-to-use font and text class that we 312 | // can use to display remaining lives and game status. 313 | sf::Font liberationSans; 314 | sf::Text textState, textLives; 315 | 316 | State state{State::GameOver}; 317 | bool pausePressedLastFrame{false}; 318 | 319 | // Let's keep track of the remaning lives in the game class. 320 | int remainingLives{0}; 321 | 322 | public: 323 | Game() 324 | { 325 | window.setFramerateLimit(60); 326 | 327 | // We need to load a font from file before using 328 | // our text objects. 329 | liberationSans.loadFromFile( 330 | R"(/usr/share/fonts/TTF/LiberationSans-Regular.ttf)"); 331 | 332 | textState.setFont(liberationSans); 333 | textState.setPosition(10, 10); 334 | textState.setCharacterSize(35.f); 335 | textState.setColor(sf::Color::White); 336 | textState.setString("Paused"); 337 | 338 | textLives.setFont(liberationSans); 339 | textLives.setPosition(10, 10); 340 | textLives.setCharacterSize(15.f); 341 | textLives.setColor(sf::Color::White); 342 | } 343 | 344 | void restart() 345 | { 346 | // Let's remember to reset the remaining lives. 347 | remainingLives = 3; 348 | 349 | state = State::Paused; 350 | manager.clear(); 351 | 352 | for(int iX{0}; iX < brkCountX; ++iX) 353 | for(int iY{0}; iY < brkCountY; ++iY) 354 | { 355 | float x{(iX + brkStartColumn) * (Brick::defWidth + brkSpacing)}; 356 | float y{(iY + brkStartRow) * (Brick::defHeight + brkSpacing)}; 357 | 358 | auto& brick(manager.create(brkOffsetX + x, y)); 359 | 360 | // Let's set the required hits for the bricks. 361 | brick.requiredHits = 1 + ((iX * iY) % 3); 362 | } 363 | 364 | manager.create(wndWidth / 2.f, wndHeight / 2.f); 365 | manager.create(wndWidth / 2, wndHeight - 50); 366 | } 367 | 368 | void run() 369 | { 370 | while(true) 371 | { 372 | window.clear(sf::Color::Black); 373 | 374 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) break; 375 | 376 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::P)) 377 | { 378 | if(!pausePressedLastFrame) 379 | { 380 | if(state == State::Paused) 381 | state = State::InProgress; 382 | else if(state == State::InProgress) 383 | state = State::Paused; 384 | } 385 | pausePressedLastFrame = true; 386 | } 387 | else 388 | pausePressedLastFrame = false; 389 | 390 | if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::R)) restart(); 391 | 392 | // If the game is not in progress, do not draw or update 393 | // game elements and display information to the player. 394 | if(state != State::InProgress) 395 | { 396 | if(state == State::Paused) 397 | textState.setString("Paused"); 398 | else if(state == State::GameOver) 399 | textState.setString("Game over!"); 400 | else if(state == State::Victory) 401 | textState.setString("You won!"); 402 | 403 | window.draw(textState); 404 | } 405 | else 406 | { 407 | // If there are no more balls on the screen, spawn a 408 | // new one and remove a life. 409 | if(manager.getAll().empty()) 410 | { 411 | manager.create(wndWidth / 2.f, wndHeight / 2.f); 412 | 413 | --remainingLives; 414 | } 415 | 416 | // If there are no more bricks on the screen, 417 | // the player won! 418 | if(manager.getAll().empty()) state = State::Victory; 419 | 420 | // If the player has no more remaining lives, 421 | // it's game over! 422 | if(remainingLives <= 0) state = State::GameOver; 423 | 424 | manager.update(); 425 | 426 | manager.forEach([this](auto& mBall) 427 | { 428 | manager.forEach([&mBall](auto& mBrick) 429 | { 430 | solveBrickBallCollision(mBrick, mBall); 431 | }); 432 | manager.forEach([&mBall](auto& mPaddle) 433 | { 434 | solvePaddleBallCollision(mPaddle, mBall); 435 | }); 436 | }); 437 | 438 | manager.refresh(); 439 | 440 | manager.draw(window); 441 | 442 | // Update lives string and draw it. 443 | textLives.setString("Lives: " + std::to_string(remainingLives)); 444 | 445 | window.draw(textLives); 446 | } 447 | 448 | window.display(); 449 | } 450 | } 451 | }; 452 | 453 | int main() 454 | { 455 | Game game; 456 | game.restart(); 457 | game.run(); 458 | return 0; 459 | } -------------------------------------------------------------------------------- /presentation.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vittorioromeo/cppcon2014/32fcf0d263976219f31c3b44bec35e3534dfe4b7/presentation.odp -------------------------------------------------------------------------------- /presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vittorioromeo/cppcon2014/32fcf0d263976219f31c3b44bec35e3534dfe4b7/presentation.pdf --------------------------------------------------------------------------------