Userland+Base: Remove Breakout and Pong games

These games were not very playable and definitely not fun.
This commit is contained in:
Andreas Kling 2022-06-14 14:17:47 +02:00
parent 4e4a930b13
commit 45de16f195
Notes: sideshowbarker 2024-07-17 10:14:01 +09:00
19 changed files with 0 additions and 1168 deletions

View file

@ -1,4 +0,0 @@
[App]
Name=Breakout
Executable=/bin/Breakout
Category=Games

View file

@ -1,4 +0,0 @@
[App]
Name=Pong
Executable=/bin/Pong
Category=Games

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 B

View file

@ -1,17 +0,0 @@
## Name
![Icon](/res/icons/16x16/app-breakout.png) Breakout
[Open](file:///bin/Breakout)
## Synopsis
```**sh
$ Breakout
```
## Description
Breakout is an arcade game from 1976 where the goal is to remove all blocks by hitting them with a ball bouncing it of a pad.
The pad can be moved by either using the mouse or the left and right arrow keys.

View file

@ -1,17 +0,0 @@
## Name
![Icon](/res/icons/16x16/app-pong.png) Pong
[Open](file:///bin/Pong)
## Synopsis
```**sh
$ Pong
```
## Description
Implementation of the 1972 Atari game Pong.
Make the ball pass behind the opponent by bouncing the ball on the players paddle which can be controlled either by the mouse position or the up and down arrow keys.

View file

@ -1,14 +0,0 @@
serenity_component(
Breakout
RECOMMENDED
TARGETS Breakout
)
set(SOURCES
main.cpp
Game.cpp
LevelSelectDialog.cpp
)
serenity_app(Breakout ICON app-breakout)
target_link_libraries(Breakout LibGUI LibMain LibDesktop)

View file

@ -1,335 +0,0 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Game.h"
#include "LevelSelectDialog.h"
#include <AK/Random.h>
#include <LibGUI/Application.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/Painter.h>
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/StandardCursor.h>
#include <unistd.h>
namespace Breakout {
Game::Game()
{
set_override_cursor(Gfx::StandardCursor::Hidden);
auto level_dialog = LevelSelectDialog::show(m_board, window());
if (level_dialog != GUI::Dialog::ExecResult::OK)
m_board = -1;
set_paused(false);
start_timer(16);
reset();
}
void Game::reset_paddle()
{
update(enclosing_int_rect(m_paddle.rect));
m_paddle.moving_left = false;
m_paddle.moving_right = false;
m_paddle.rect = { game_width / 2 - 40, game_height - 20, 80, 16 };
update(enclosing_int_rect(m_paddle.rect));
}
void Game::reset()
{
update(lives_left_rect());
m_lives = 3;
update(lives_left_rect());
m_pause_count = 0;
m_cheater = false;
reset_ball();
reset_paddle();
generate_bricks();
}
void Game::generate_bricks()
{
m_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,
};
Vector<Brick> boards[] = {
// :^)
Vector({
Brick(0, 0, colors[3], 40, 12, 100),
Brick(0, 4, colors[3], 40, 12, 100),
Brick(1, 2, colors[3], 40, 12, 100),
Brick(1, 5, colors[3], 40, 12, 100),
Brick(2, 1, colors[3], 40, 12, 100),
Brick(2, 3, colors[3], 40, 12, 100),
Brick(2, 6, colors[3], 40, 12, 100),
Brick(3, 6, colors[3], 40, 12, 100),
Brick(4, 0, colors[3], 40, 12, 100),
Brick(4, 6, colors[3], 40, 12, 100),
Brick(5, 6, colors[3], 40, 12, 100),
Brick(6, 5, colors[3], 40, 12, 100),
Brick(7, 4, colors[3], 40, 12, 100),
})
};
if (m_board != -1) {
m_bricks = boards[m_board];
for (auto& brick : m_bricks)
update(enclosing_int_rect(brick.rect));
} else {
// Rainbow
for (int row = 0; row < 7; ++row) {
for (int column = 0; column < 10; ++column) {
Brick brick(row, column, colors[row]);
m_bricks.append(brick);
update(enclosing_int_rect(brick.rect));
}
}
}
}
void Game::set_paused(bool paused)
{
m_paused = paused;
if (m_paused) {
set_override_cursor(Gfx::StandardCursor::None);
m_pause_count++;
} else {
set_override_cursor(Gfx::StandardCursor::Hidden);
}
update(pause_rect());
}
void Game::timer_event(Core::TimerEvent&)
{
if (m_paused)
return;
tick();
}
void Game::paint_event(GUI::PaintEvent& event)
{
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
Gfx::AntiAliasingPainter aa_painter { painter };
painter.fill_rect(rect(), Color::Black);
aa_painter.fill_ellipse(enclosing_int_rect(m_ball.rect()), Color::Red);
painter.fill_rect(enclosing_int_rect(m_paddle.rect), Color::White);
for (auto& brick : m_bricks) {
if (!brick.dead)
painter.fill_rect(enclosing_int_rect(brick.rect), brick.color);
}
painter.draw_text(lives_left_rect(), String::formatted("Lives: {}", m_lives), Gfx::TextAlignment::Center, Color::White);
if (m_paused) {
char const* msg = m_cheater ? "C H E A T E R" : "P A U S E D";
painter.draw_text(pause_rect(), msg, Gfx::TextAlignment::Center, Color::White);
}
}
void Game::keyup_event(GUI::KeyEvent& event)
{
if (m_paused)
return;
switch (event.key()) {
case Key_A:
[[fallthrough]];
case Key_Left:
m_paddle.moving_left = false;
break;
case Key_D:
[[fallthrough]];
case Key_Right:
m_paddle.moving_right = false;
break;
default:
break;
}
}
void Game::keydown_event(GUI::KeyEvent& event)
{
if (m_paused)
return;
switch (event.key()) {
case Key_Escape:
GUI::Application::the()->quit();
break;
case Key_A:
[[fallthrough]];
case Key_Left:
m_paddle.moving_left = true;
break;
case Key_D:
[[fallthrough]];
case Key_Right:
m_paddle.moving_right = true;
break;
default:
break;
}
}
void Game::mousemove_event(GUI::MouseEvent& event)
{
if (m_paused)
return;
update(enclosing_int_rect(m_paddle.rect));
float new_paddle_x = event.x() - m_paddle.rect.width() / 2;
new_paddle_x = max(0.0f, new_paddle_x);
new_paddle_x = min(game_width - m_paddle.rect.width(), new_paddle_x);
m_paddle.rect.set_x(new_paddle_x);
update(enclosing_int_rect(m_paddle.rect));
}
void Game::reset_ball()
{
int position_x_min = (game_width / 2) - 50;
int position_x_max = (game_width / 2) + 50;
int position_x = get_random<u32>() % (position_x_max - position_x_min + 1) + position_x_min;
int position_y = 200;
int velocity_x = get_random<u32>() % 3 + 1;
int velocity_y = 3 + (3 - velocity_x);
if (get_random<u32>() % 2)
velocity_x = velocity_x * -1;
update(enclosing_int_rect(m_ball.rect()));
m_ball = {};
m_ball.position = { position_x, position_y };
m_ball.velocity = { velocity_x, velocity_y };
update(enclosing_int_rect(m_ball.rect()));
}
void Game::hurt()
{
stop_timer();
update(lives_left_rect());
m_lives--;
update(lives_left_rect());
if (m_lives <= 0) {
GUI::MessageBox::show(window(), "You lose!", "Breakout", GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::OK);
reset();
}
sleep(1);
reset_ball();
reset_paddle();
start_timer(16);
}
void Game::win()
{
stop_timer();
update();
if (m_cheater) {
GUI::MessageBox::show(window(), "You cheated not only the game, but yourself.", "Breakout", GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::OK);
} else {
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.position += new_ball.velocity;
update(enclosing_int_rect(m_ball.rect()));
if (new_ball.x() < new_ball.radius || new_ball.x() > game_width - new_ball.radius) {
new_ball.position.set_x(m_ball.x());
new_ball.velocity.set_x(new_ball.velocity.x() * -1);
}
if (new_ball.y() < new_ball.radius) {
new_ball.position.set_y(m_ball.y());
new_ball.velocity.set_y(new_ball.velocity.y() * -1);
}
if (new_ball.y() > game_height - new_ball.radius) {
hurt();
return;
}
update(enclosing_int_rect(new_ball.rect()));
if (new_ball.rect().intersects(m_paddle.rect)) {
if (m_ball.y() < new_ball.y()) {
new_ball.position.set_y(m_ball.y());
}
new_ball.velocity.set_y(fabs(new_ball.velocity.y()) * -1);
float distance_to_middle_of_paddle = new_ball.x() - m_paddle.rect.center().x();
float relative_impact_point = distance_to_middle_of_paddle / m_paddle.rect.width();
new_ball.velocity.set_x(relative_impact_point * 7);
}
for (auto& brick : m_bricks) {
if (brick.dead)
continue;
if (new_ball.rect().intersects(brick.rect)) {
brick.dead = true;
auto overlap = new_ball.rect().intersected(brick.rect);
if (overlap.width() < overlap.height()) {
new_ball.position.set_x(m_ball.x());
new_ball.velocity.set_x(new_ball.velocity.x() * -1);
} else {
new_ball.position.set_y(m_ball.y());
new_ball.velocity.set_y(new_ball.velocity.y() * -1);
}
update(enclosing_int_rect(brick.rect));
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) {
update(enclosing_int_rect(m_paddle.rect));
m_paddle.rect.set_x(max(0.0f, m_paddle.rect.x() - m_paddle.speed));
update(enclosing_int_rect(m_paddle.rect));
}
if (m_paddle.moving_right) {
update(enclosing_int_rect(m_paddle.rect));
m_paddle.rect.set_x(min(game_width - m_paddle.rect.width(), m_paddle.rect.x() + m_paddle.speed));
update(enclosing_int_rect(m_paddle.rect));
}
m_ball = new_ball;
if (m_pause_count > 50)
m_cheater = true;
}
}

View file

@ -1,105 +0,0 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGUI/Widget.h>
#include <LibGfx/Font/Font.h>
namespace Breakout {
class Game final : public GUI::Widget {
C_OBJECT(Game);
public:
static constexpr int game_width = 480;
static constexpr int game_height = 500;
virtual ~Game() override = default;
void set_paused(bool paused);
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 {
Gfx::FloatPoint position;
Gfx::FloatPoint velocity;
float radius { 8 };
float x() const { return position.x(); }
float y() const { return position.y(); }
Gfx::FloatRect rect() const
{
return { x() - radius, y() - radius, radius * 2, radius * 2 };
}
};
struct Paddle {
Gfx::FloatRect rect;
float speed { 5 };
bool moving_left { false };
bool moving_right { false };
};
struct Brick {
Gfx::FloatRect rect;
Gfx::Color color;
bool dead { false };
Brick(int row, int column, Gfx::Color c, int brick_width = 40, int brick_height = 12, int field_left_offset = 30, int field_top_offset = 30, int brick_spacing = 3)
{
rect = {
field_left_offset + (column * brick_width) + (column * brick_spacing),
field_top_offset + (row * brick_height) + (row * brick_spacing),
brick_width,
brick_height
};
color = c;
}
};
Gfx::IntRect lives_left_rect() const
{
int msg_width = font().width(String::formatted("Lives: {}", m_lives));
return { (game_width - msg_width - 2), 2, msg_width, font().glyph_height() };
}
Gfx::IntRect pause_rect() const
{
char const* msg = m_cheater ? "C H E A T E R" : "P A U S E D";
int msg_width = font().width(msg);
int msg_height = font().glyph_height();
return { (game_width / 2) - (msg_width / 2), (game_height / 2) - (msg_height / 2), msg_width, msg_height };
}
bool m_paused;
int m_lives;
int m_board;
long m_pause_count;
bool m_cheater;
Ball m_ball;
Paddle m_paddle;
Vector<Brick> m_bricks;
};
}

View file

@ -1,58 +0,0 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "LevelSelectDialog.h"
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/Label.h>
#include <LibGUI/ListView.h>
namespace Breakout {
LevelSelectDialog::LevelSelectDialog(Window* parent_window)
: Dialog(parent_window)
{
set_rect(0, 0, 300, 250);
set_title("Level Select");
build();
}
GUI::Dialog::ExecResult LevelSelectDialog::show(int& board_number, Window* parent_window)
{
auto box = LevelSelectDialog::construct(parent_window);
box->set_resizable(false);
if (parent_window)
box->set_icon(parent_window->icon());
auto result = box->exec();
board_number = box->level();
return result;
}
void LevelSelectDialog::build()
{
auto& main_widget = set_main_widget<GUI::Widget>();
main_widget.set_fill_with_background_color(true);
auto& layout = main_widget.set_layout<GUI::VerticalBoxLayout>();
layout.set_margins(4);
main_widget.add<GUI::Label>("Choose a level").set_text_alignment(Gfx::TextAlignment::Center);
auto& level_list = main_widget.add<GUI::Widget>();
auto& scroll_layout = level_list.set_layout<GUI::VerticalBoxLayout>();
scroll_layout.set_spacing(4);
level_list.add<GUI::Button>("Rainbow").on_click = [this](auto) {
m_level = -1;
done(ExecResult::OK);
};
level_list.add<GUI::Button>(":^)").on_click = [this](auto) {
m_level = 0;
done(ExecResult::OK);
};
}
}

View file

@ -1,25 +0,0 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGUI/Dialog.h>
namespace Breakout {
class LevelSelectDialog : public GUI::Dialog {
C_OBJECT(LevelSelectDialog)
public:
virtual ~LevelSelectDialog() override = default;
static ExecResult show(int& board_number, Window* parent_window);
int level() const { return m_level; }
private:
explicit LevelSelectDialog(Window* parent_window);
void build();
int m_level;
};
}

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Game.h"
#include <AK/URL.h>
#include <LibCore/System.h>
#include <LibDesktop/Launcher.h>
#include <LibGUI/Application.h>
#include <LibGUI/Icon.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Menubar.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
#include <LibMain/Main.h>
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
TRY(Core::System::pledge("stdio recvfd sendfd rpath unix"));
auto app = TRY(GUI::Application::try_create(arguments));
TRY(Desktop::Launcher::add_allowed_handler_with_only_specific_urls("/bin/Help", { URL::create_with_file_protocol("/usr/share/man/man6/Breakout.md") }));
TRY(Desktop::Launcher::seal_allowlist());
TRY(Core::System::pledge("stdio recvfd sendfd rpath"));
TRY(Core::System::unveil("/res", "r"));
TRY(Core::System::unveil("/tmp/portal/launch", "rw"));
TRY(Core::System::unveil(nullptr, nullptr));
auto window = TRY(GUI::Window::try_create());
window->resize(Breakout::Game::game_width, Breakout::Game::game_height);
window->set_resizable(false);
window->set_double_buffering_enabled(false);
window->set_title("Breakout");
auto app_icon = TRY(GUI::Icon::try_create_default_icon("app-breakout"));
window->set_icon(app_icon.bitmap_for_size(16));
auto game = TRY(window->try_set_main_widget<Breakout::Game>());
auto game_menu = TRY(window->try_add_menu("&Game"));
TRY(game_menu->try_add_action(GUI::Action::create_checkable("&Pause", { {}, Key_P }, [&](auto& action) {
game->set_paused(action.is_checked());
})));
TRY(game_menu->try_add_separator());
TRY(game_menu->try_add_action(GUI::CommonActions::make_quit_action([](auto&) {
GUI::Application::the()->quit();
})));
auto help_menu = TRY(window->try_add_menu("&Help"));
TRY(help_menu->try_add_action(GUI::CommonActions::make_help_action([](auto&) {
Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man6/Breakout.md"), "/bin/Help");
})));
TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Breakout", app_icon, window)));
window->show();
return app->exec();
}

View file

@ -1,12 +1,10 @@
add_subdirectory(2048)
add_subdirectory(Breakout)
add_subdirectory(Chess)
add_subdirectory(FlappyBug)
add_subdirectory(GameOfLife)
add_subdirectory(Hearts)
add_subdirectory(MasterWord)
add_subdirectory(Minesweeper)
add_subdirectory(Pong)
add_subdirectory(Snake)
add_subdirectory(Solitaire)
add_subdirectory(Spider)

View file

@ -1,13 +0,0 @@
serenity_component(
Pong
RECOMMENDED
TARGETS Pong
)
set(SOURCES
main.cpp
Game.cpp
)
serenity_app(Pong ICON app-pong)
target_link_libraries(Pong LibGUI LibMain LibDesktop)

View file

@ -1,325 +0,0 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Game.h"
#include <AK/Random.h>
#include <LibGfx/AntiAliasingPainter.h>
namespace Pong {
Game::Game()
{
start_timer(16);
reset();
}
void Game::reset_keys()
{
m_up_key_held = false;
m_down_key_held = false;
}
void Game::reset_paddles()
{
if (m_cursor_paddle_target_y.has_value())
update(cursor_paddle_target_rect());
m_cursor_paddle_target_y.clear();
update(enclosing_int_rect(m_player1_paddle.rect));
m_player1_paddle.moving_up = m_up_key_held;
m_player1_paddle.moving_down = m_down_key_held;
m_player1_paddle.rect = { game_width - 12, game_height / 2 - 40, m_player1_paddle.width, m_player1_paddle.height };
update(enclosing_int_rect(m_player1_paddle.rect));
update(enclosing_int_rect(m_player2_paddle.rect));
m_player2_paddle.moving_up = false;
m_player2_paddle.moving_down = false;
m_player2_paddle.rect = { 4, game_height / 2 - 40, m_player2_paddle.width, m_player2_paddle.height };
update(enclosing_int_rect(m_player2_paddle.rect));
}
void Game::reset()
{
if (m_game_over) {
m_game_over = false;
start_timer(16);
}
// Make sure the current ball disappears.
update(enclosing_int_rect(m_ball.rect()));
reset_scores();
reset_ball(1);
reset_keys();
reset_paddles();
}
void Game::timer_event(Core::TimerEvent&)
{
tick();
}
void Game::paint_event(GUI::PaintEvent& event)
{
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
Gfx::AntiAliasingPainter aa_painter { painter };
painter.fill_rect(rect(), Color::Black);
painter.fill_rect(enclosing_int_rect(m_net.rect()), m_net.color);
aa_painter.fill_ellipse(enclosing_int_rect(m_ball.rect()), Color::Red);
painter.fill_rect(enclosing_int_rect(m_player1_paddle.rect), m_player1_paddle.color);
painter.fill_rect(enclosing_int_rect(m_player2_paddle.rect), m_player2_paddle.color);
if (m_cursor_paddle_target_y.has_value())
aa_painter.fill_ellipse(cursor_paddle_target_rect(), Color::Blue);
painter.draw_text(player_1_score_rect(), String::formatted("{}", m_player_1_score), Gfx::TextAlignment::TopLeft, Color::White);
painter.draw_text(player_2_score_rect(), String::formatted("{}", m_player_2_score), Gfx::TextAlignment::TopLeft, Color::White);
}
void Game::keyup_event(GUI::KeyEvent& event)
{
switch (event.key()) {
case Key_W:
case Key_Up:
m_up_key_held = false;
m_player1_paddle.moving_up = false;
break;
case Key_S:
case Key_Down:
m_down_key_held = false;
m_player1_paddle.moving_down = false;
break;
default:
break;
}
}
void Game::keydown_event(GUI::KeyEvent& event)
{
switch (event.key()) {
case Key_Escape:
GUI::Application::the()->quit();
break;
case Key_W:
case Key_Up:
m_up_key_held = true;
m_player1_paddle.moving_up = true;
m_player1_paddle.moving_down = false;
m_cursor_paddle_target_y.clear();
break;
case Key_S:
case Key_Down:
m_down_key_held = true;
m_player1_paddle.moving_up = false;
m_player1_paddle.moving_down = true;
m_cursor_paddle_target_y.clear();
break;
default:
break;
}
}
void Game::track_mouse_move(Gfx::IntPoint const& point)
{
if (m_up_key_held || m_down_key_held) {
// We're using the keyboard to move the paddle, the cursor is doing something else
return;
}
if (m_cursor_paddle_target_y.has_value())
update(cursor_paddle_target_rect());
auto relative_point = point - window()->position();
m_cursor_paddle_target_y = clamp(relative_point.y() - m_player1_paddle.rect.height() / 2, 0.f, game_height - m_player1_paddle.rect.height());
if (m_player1_paddle.rect.y() > *m_cursor_paddle_target_y) {
m_player1_paddle.moving_up = true;
m_player1_paddle.moving_down = false;
} else if (m_player1_paddle.rect.y() < *m_cursor_paddle_target_y) {
m_player1_paddle.moving_up = false;
m_player1_paddle.moving_down = true;
}
update(cursor_paddle_target_rect());
}
void Game::reset_scores()
{
// Clearing the scores first would lead to overly narrow rects for multi-digit scores.
update(player_1_score_rect());
update(player_2_score_rect());
m_player_1_score = 0;
m_player_2_score = 0;
}
void Game::reset_ball(int serve_to_player)
{
int position_y_min = (game_width / 2) - 50;
int position_y_max = (game_width / 2) + 50;
int position_y = get_random<u32>() % (position_y_max - position_y_min + 1) + position_y_min;
int position_x = (game_height / 2);
int velocity_y = get_random<u32>() % 3 + 1;
int velocity_x = 4 + (5 - velocity_y);
if (get_random<u32>() % 2)
velocity_y = velocity_y * -1;
if (serve_to_player == 2)
velocity_x = velocity_x * -1;
m_ball = {};
m_ball.position = { position_x, position_y };
m_ball.velocity = { velocity_x, velocity_y };
}
void Game::show_game_over_message(int winner)
{
GUI::MessageBox::show(window(), String::formatted("Player {} wins!", winner), "Pong", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OK);
}
void Game::round_over(int winner)
{
stop_timer();
if (winner == 1) {
update(player_1_score_rect());
m_player_1_score++;
update(player_1_score_rect());
}
if (winner == 2) {
update(player_2_score_rect());
m_player_2_score++;
update(player_2_score_rect());
}
if (m_player_1_score == m_score_to_win || m_player_2_score == m_score_to_win) {
m_game_over = true;
show_game_over_message(winner);
return;
}
reset_ball(winner);
reset_paddles();
start_timer(16);
}
void Game::calculate_move()
{
int player_2_paddle_top = m_player2_paddle.rect.top();
int player_2_paddle_bottom = m_player2_paddle.rect.bottom();
if (m_ball.velocity.x() > 0 || m_ball.x() > game_width / 2) {
// The ball is in the opponent's court, relax.
m_player2_paddle.moving_up = false;
m_player2_paddle.moving_down = false;
return;
}
int ball_position = m_ball.y() + m_ball.radius;
// AI paddle begins moving when the ball crosses the begin_trigger,
// but stops only if it crosses the end_trigger. end_trigger forces
// overcorrection, so that the paddle moves more smoothly.
int begin_trigger = m_player2_paddle.rect.height() / 4;
int end_trigger = m_player2_paddle.rect.height() / 2;
if (m_player2_paddle.moving_up) {
if (player_2_paddle_top + end_trigger < ball_position)
m_player2_paddle.moving_up = false;
} else {
if (player_2_paddle_top + begin_trigger > ball_position)
m_player2_paddle.moving_up = true;
}
if (m_player2_paddle.moving_down) {
if (player_2_paddle_bottom - end_trigger > ball_position)
m_player2_paddle.moving_down = false;
} else {
if (player_2_paddle_bottom - begin_trigger < ball_position)
m_player2_paddle.moving_down = true;
}
}
void Game::tick()
{
auto new_ball = m_ball;
new_ball.position += new_ball.velocity;
update(enclosing_int_rect(m_ball.rect()));
if (new_ball.y() < new_ball.radius || new_ball.y() > game_height - new_ball.radius) {
new_ball.position.set_y(m_ball.y());
new_ball.velocity.set_y(new_ball.velocity.y() * -1);
}
if (new_ball.x() < new_ball.radius) {
round_over(1);
return;
}
if (new_ball.x() > (game_width - new_ball.radius)) {
round_over(2);
return;
}
update(enclosing_int_rect(new_ball.rect()));
if (new_ball.rect().intersects(m_player1_paddle.rect)) {
new_ball.position.set_x(m_ball.x());
new_ball.velocity.set_x(new_ball.velocity.x() * -1);
float distance_to_middle_of_paddle = new_ball.y() - m_player1_paddle.rect.center().y();
float relative_impact_point = distance_to_middle_of_paddle / m_player1_paddle.rect.height();
new_ball.velocity.set_y(relative_impact_point * 7);
}
if (new_ball.rect().intersects(m_player2_paddle.rect)) {
new_ball.position.set_x(m_ball.x());
new_ball.velocity.set_x(new_ball.velocity.x() * -1);
float distance_to_middle_of_paddle = new_ball.y() - m_player2_paddle.rect.center().y();
float relative_impact_point = distance_to_middle_of_paddle / m_player2_paddle.rect.height();
new_ball.velocity.set_y(relative_impact_point * 7);
}
if (m_player1_paddle.moving_up) {
update(enclosing_int_rect(m_player1_paddle.rect));
m_player1_paddle.rect.set_y(max(0.0f, m_player1_paddle.rect.y() - m_player1_paddle.speed));
if (m_cursor_paddle_target_y.has_value() && m_player1_paddle.rect.y() <= *m_cursor_paddle_target_y) {
m_cursor_paddle_target_y.clear();
m_player1_paddle.moving_up = false;
}
update(enclosing_int_rect(m_player1_paddle.rect));
}
if (m_player1_paddle.moving_down) {
update(enclosing_int_rect(m_player1_paddle.rect));
m_player1_paddle.rect.set_y(min(game_height - m_player1_paddle.rect.height(), m_player1_paddle.rect.y() + m_player1_paddle.speed));
if (m_cursor_paddle_target_y.has_value() && m_player1_paddle.rect.y() >= *m_cursor_paddle_target_y) {
m_cursor_paddle_target_y.clear();
m_player1_paddle.moving_down = false;
}
update(enclosing_int_rect(m_player1_paddle.rect));
}
calculate_move();
if (m_player2_paddle.moving_up) {
update(enclosing_int_rect(m_player2_paddle.rect));
m_player2_paddle.rect.set_y(max(0.0f, m_player2_paddle.rect.y() - m_player2_paddle.speed));
update(enclosing_int_rect(m_player2_paddle.rect));
}
if (m_player2_paddle.moving_down) {
update(enclosing_int_rect(m_player2_paddle.rect));
m_player2_paddle.rect.set_y(min(game_height - m_player2_paddle.rect.height(), m_player2_paddle.rect.y() + m_player2_paddle.speed));
update(enclosing_int_rect(m_player2_paddle.rect));
}
m_ball = new_ball;
}
}

View file

@ -1,121 +0,0 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <LibGUI/Application.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/MouseTracker.h>
#include <LibGUI/Painter.h>
#include <LibGUI/Widget.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/StandardCursor.h>
namespace Pong {
class Game final : public GUI::Widget
, GUI::MouseTracker {
C_OBJECT(Game);
public:
static constexpr int game_width = 560;
static constexpr int game_height = 480;
virtual ~Game() override = default;
void reset();
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 timer_event(Core::TimerEvent&) override;
virtual void track_mouse_move(Gfx::IntPoint const&) override;
void reset_scores();
void reset_ball(int serve_to_player);
void reset_keys();
void reset_paddles();
void tick();
void round_over(int player);
void show_game_over_message(int player);
void calculate_move();
struct Ball {
Gfx::FloatPoint position;
Gfx::FloatPoint velocity;
float radius { 4 };
float x() const { return position.x(); }
float y() const { return position.y(); }
Gfx::FloatRect rect() const
{
return { x() - radius, y() - radius, radius * 2, radius * 2 };
}
};
struct Paddle {
Gfx::FloatRect rect;
float speed { 5 };
float width { 8 };
float height { 28 };
bool moving_up { false };
bool moving_down { false };
Gfx::Color color { Color::White };
};
struct Net {
Gfx::Color color { Color::White };
Gfx::FloatRect rect() const
{
return { (game_width / 2) - 1, 0, 2, game_height };
}
};
constexpr static int score_margin = 5;
Gfx::IntRect player_1_score_rect() const
{
int score_width = font().width(String::formatted("{}", m_player_1_score));
return { (game_width / 2) + score_margin, score_margin, score_width, font().glyph_height() };
}
Gfx::IntRect player_2_score_rect() const
{
int score_width = font().width(String::formatted("{}", m_player_2_score));
return { (game_width / 2) - score_width - score_margin, score_margin, score_width, font().glyph_height() };
}
Gfx::IntRect cursor_paddle_target_rect() const
{
int radius = 3;
int center_x = m_player1_paddle.rect.center().x();
int center_y = *m_cursor_paddle_target_y + m_player1_paddle.rect.height() / 2;
return { center_x - radius, center_y - radius, 2 * radius, 2 * radius };
}
Net m_net;
Ball m_ball;
Paddle m_player1_paddle;
Paddle m_player2_paddle;
int m_score_to_win = 21;
int m_player_1_score = 0;
int m_player_2_score = 0;
Optional<int> m_cursor_paddle_target_y;
bool m_up_key_held = false;
bool m_down_key_held = false;
bool m_game_over = false;
};
}

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Game.h"
#include <AK/URL.h>
#include <LibCore/System.h>
#include <LibDesktop/Launcher.h>
#include <LibGUI/Application.h>
#include <LibGUI/Icon.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Menubar.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
#include <LibMain/Main.h>
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
TRY(Core::System::pledge("stdio rpath recvfd sendfd unix"));
auto app = TRY(GUI::Application::try_create(arguments));
TRY(Desktop::Launcher::add_allowed_handler_with_only_specific_urls("/bin/Help", { URL::create_with_file_protocol("/usr/share/man/man6/Pong.md") }));
TRY(Desktop::Launcher::seal_allowlist());
TRY(Core::System::pledge("stdio rpath recvfd sendfd"));
TRY(Core::System::unveil("/res", "r"));
TRY(Core::System::unveil("/tmp/portal/launch", "rw"));
TRY(Core::System::unveil(nullptr, nullptr));
auto window = TRY(GUI::Window::try_create());
window->resize(Pong::Game::game_width, Pong::Game::game_height);
auto app_icon = TRY(GUI::Icon::try_create_default_icon("app-pong"));
window->set_icon(app_icon.bitmap_for_size(16));
window->set_title("Pong");
window->set_double_buffering_enabled(false);
auto game = TRY(window->try_set_main_widget<Pong::Game>());
window->set_resizable(false);
auto game_menu = TRY(window->try_add_menu("&Game"));
TRY(game_menu->try_add_action(GUI::Action::create("&New Game", { Mod_None, Key_F2 }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/reload.png")), [&](auto&) {
game->reset();
})));
TRY(game_menu->try_add_separator());
TRY(game_menu->try_add_action(GUI::CommonActions::make_quit_action([](auto&) {
GUI::Application::the()->quit();
})));
auto help_menu = TRY(window->try_add_menu("&Help"));
TRY(help_menu->try_add_action(GUI::CommonActions::make_help_action([](auto&) {
Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man6/Pong.md"), "/bin/Help");
})));
TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Pong", app_icon, window)));
window->show();
return app->exec();
}