diff --git a/Games/Breakout/CMakeLists.txt b/Games/Breakout/CMakeLists.txt new file mode 100644 index 0000000000..7c34f1148e --- /dev/null +++ b/Games/Breakout/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES + main.cpp + Game.cpp +) + +serenity_bin(Breakout) +target_link_libraries(Breakout LibGUI) diff --git a/Games/Breakout/Game.cpp b/Games/Breakout/Game.cpp new file mode 100644 index 0000000000..8588cac726 --- /dev/null +++ b/Games/Breakout/Game.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Game.h" +#include +#include +#include +#include + +namespace Breakout { + +Game::Game() +{ + set_override_cursor(Gfx::StandardCursor::Hidden); + start_timer(16); + reset(); +} + +Game::~Game() +{ +} + +void Game::reset_paddle() +{ + m_paddle.rect = { game_width / 2 - 40, game_height - 20, 80, 16 }; +} + +void Game::reset() +{ + reset_ball(); + reset_paddle(); + generate_bricks(); +} + +void Game::generate_bricks() +{ + Gfx::Color colors[] = { + Gfx::Color::Red, + Gfx::Color::Green, + Gfx::Color::Blue, + Gfx::Color::Yellow, + Gfx::Color::Magenta, + Gfx::Color::Cyan, + Gfx::Color::LightGray, + }; + + int brick_width = 40; + int brick_height = 12; + int brick_spacing = 3; + int field_left_offset = 30; + int field_top_offset = 30; + + for (int row = 0; row < 7; ++row) { + for (int column = 0; column < 10; ++column) { + Brick brick; + brick.rect = { + field_left_offset + (column * brick_width) + (column * brick_spacing), + field_top_offset + (row * brick_height) + (row * brick_spacing), + brick_width, + brick_height + }; + brick.color = colors[row]; + m_bricks.append(brick); + } + } +} + +void Game::timer_event(Core::TimerEvent&) +{ + tick(); +} + +void Game::paint_event(GUI::PaintEvent& event) +{ + GUI::Painter painter(*this); + painter.add_clip_rect(event.rect()); + + painter.fill_rect(rect(), Color::Black); + + painter.fill_ellipse(m_ball.rect(), Color::Red); + + painter.fill_rect(m_paddle.rect, Color::White); + + for (auto& brick : m_bricks) { + if (!brick.dead) + painter.fill_rect(brick.rect, brick.color); + } +} + +void Game::keyup_event(GUI::KeyEvent& event) +{ + switch (event.key()) { + case Key_Left: + m_paddle.moving_left = false; + break; + case Key_Right: + m_paddle.moving_right = false; + break; + default: + break; + } +} + +void Game::keydown_event(GUI::KeyEvent& event) +{ + switch (event.key()) { + case Key_Escape: + GUI::Application::the()->quit(); + break; + case Key_Left: + m_paddle.moving_left = true; + break; + case Key_Right: + m_paddle.moving_right = true; + break; + default: + break; + } +} + +void Game::mousemove_event(GUI::MouseEvent& event) +{ + int new_paddle_x = event.x() - m_paddle.rect.width() / 2; + new_paddle_x = max(0, new_paddle_x); + new_paddle_x = min(game_width - m_paddle.rect.width(), new_paddle_x); + m_paddle.rect.set_x(new_paddle_x); +} + +void Game::reset_ball() +{ + m_ball = {}; + m_ball.x = 150; + m_ball.y = 200; + m_ball.vx = 3; + m_ball.vy = 3; +} + +void Game::hurt() +{ + stop_timer(); + GUI::MessageBox::show(window(), "Ouch!", "Breakout", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OK); + reset_ball(); + reset_paddle(); + start_timer(16); +} + +void Game::win() +{ + stop_timer(); + GUI::MessageBox::show(window(), "You win!", "Breakout", GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::OK); + reset(); + start_timer(16); +} + +void Game::tick() +{ + auto new_ball = m_ball; + new_ball.x += new_ball.vx; + new_ball.y += new_ball.vy; + + if (new_ball.x < new_ball.radius || new_ball.x > game_width - new_ball.radius) { + new_ball.x = m_ball.x; + new_ball.vx *= -1; + } + + if (new_ball.y < new_ball.radius) { + new_ball.y = m_ball.y; + new_ball.vy *= -1; + } + + if (new_ball.y > game_height - new_ball.radius) { + hurt(); + return; + } + + if (new_ball.rect().intersects(m_paddle.rect)) { + new_ball.y = m_ball.y; + new_ball.vy *= -1; + } + + for (auto& brick : m_bricks) { + if (brick.dead) + continue; + if (m_ball.rect().intersects(brick.rect)) { + brick.dead = true; + + auto overlap = m_ball.rect().intersected(brick.rect); + if (overlap.width() < overlap.height()) { + new_ball.x = m_ball.x; + new_ball.vx *= -1; + } else { + new_ball.y = m_ball.y; + new_ball.vy *= -1; + } + break; + } + } + + bool has_live_bricks = false; + for (auto& brick : m_bricks) { + if (!brick.dead) { + has_live_bricks = true; + break; + } + } + + if (!has_live_bricks) { + win(); + return; + } + + if (m_paddle.moving_left) { + m_paddle.rect.set_x(max(0, m_paddle.rect.x() - m_paddle.speed)); + } + if (m_paddle.moving_right) { + m_paddle.rect.set_x(min(game_width - m_paddle.rect.width(), m_paddle.rect.x() + m_paddle.speed)); + } + + m_ball = new_ball; + + update(); +} + +} diff --git a/Games/Breakout/Game.h b/Games/Breakout/Game.h new file mode 100644 index 0000000000..29da037d5a --- /dev/null +++ b/Games/Breakout/Game.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace Breakout { + +class Game final : public GUI::Widget { + C_OBJECT(Game); + +public: + static const int game_width = 480; + static const int game_height = 500; + + virtual ~Game() override; + +private: + Game(); + + virtual void paint_event(GUI::PaintEvent&) override; + virtual void keyup_event(GUI::KeyEvent&) override; + virtual void keydown_event(GUI::KeyEvent&) override; + virtual void mousemove_event(GUI::MouseEvent&) override; + virtual void timer_event(Core::TimerEvent&) override; + + void reset(); + void reset_ball(); + void reset_paddle(); + void generate_bricks(); + void tick(); + void hurt(); + void win(); + + struct Ball { + int x { 0 }; + int y { 0 }; + int radius { 8 }; + int vx { 0 }; + int vy { 0 }; + + Gfx::IntRect rect() const + { + return { x - radius, y - radius, radius * 2, radius * 2 }; + } + }; + + struct Paddle { + Gfx::IntRect rect; + int speed { 5 }; + bool moving_left { false }; + bool moving_right { false }; + }; + + struct Brick { + Gfx::IntRect rect; + Gfx::Color color; + bool dead { false }; + }; + + Ball m_ball; + Paddle m_paddle; + Vector m_bricks; +}; + +} diff --git a/Games/Breakout/main.cpp b/Games/Breakout/main.cpp new file mode 100644 index 0000000000..1fa509e1b2 --- /dev/null +++ b/Games/Breakout/main.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Game.h" +#include +#include + +int main(int argc, char** argv) +{ + auto app = GUI::Application::construct(argc, argv); + auto window = GUI::Window::construct(); + window->resize(Breakout::Game::game_width, Breakout::Game::game_height); + window->set_title("Breakout"); + window->set_main_widget(); + window->show(); + return app->exec(); +} diff --git a/Games/CMakeLists.txt b/Games/CMakeLists.txt index 02ffed9a9d..50e0701cfd 100644 --- a/Games/CMakeLists.txt +++ b/Games/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(2048) +add_subdirectory(Breakout) add_subdirectory(Chess) add_subdirectory(Minesweeper) add_subdirectory(Snake)