summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Wilson <ryan@rdwilson.xyz>2021-05-22 05:54:58 -0300
committerGitHub <noreply@github.com>2021-05-22 09:54:58 +0100
commit1965d60aebf3ee9cc4631b277eaf37bc8edd92f2 (patch)
treef3ac07ab9dde088205ec08e1a1d540224b7aa86c
parentb808815e57fe680ec2d98ac2dc5bccd610e871ca (diff)
downloadserenity-1965d60aebf3ee9cc4631b277eaf37bc8edd92f2.zip
GameOfLife: Add choosable patterns
-rw-r--r--Userland/Games/GameOfLife/BoardWidget.cpp218
-rw-r--r--Userland/Games/GameOfLife/BoardWidget.h23
-rw-r--r--Userland/Games/GameOfLife/CMakeLists.txt3
-rw-r--r--Userland/Games/GameOfLife/Pattern.cpp39
-rw-r--r--Userland/Games/GameOfLife/Pattern.h26
-rw-r--r--Userland/Games/GameOfLife/main.cpp7
6 files changed, 309 insertions, 7 deletions
diff --git a/Userland/Games/GameOfLife/BoardWidget.cpp b/Userland/Games/GameOfLife/BoardWidget.cpp
index a8a7c1fb45..aca006bf9b 100644
--- a/Userland/Games/GameOfLife/BoardWidget.cpp
+++ b/Userland/Games/GameOfLife/BoardWidget.cpp
@@ -1,22 +1,36 @@
/*
* Copyright (c) 2021, Andres Crucitti <dasc495@gmail.com>
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2021, Ryan Wilson <ryan@rdwilson.xyz>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "BoardWidget.h"
+#include <LibGUI/Menu.h>
#include <LibGUI/Painter.h>
BoardWidget::BoardWidget(size_t rows, size_t columns)
: m_board(make<Board>(rows, columns))
{
m_timer = add<Core::Timer>();
+ m_pattern_preview_timer = add<Core::Timer>();
m_timer->stop();
+ m_pattern_preview_timer->stop();
m_timer->on_timeout = [this] {
run_generation();
};
+ m_pattern_preview_timer->on_timeout = [this] {
+ update();
+ };
m_timer->set_interval(m_running_timer_interval);
+ m_pattern_preview_timer->set_interval(m_running_pattern_preview_timer_interval);
+
+ on_pattern_selection = [this](auto* pattern) {
+ m_selected_pattern = pattern;
+ };
+
+ setup_patterns();
}
void BoardWidget::run_generation()
@@ -128,6 +142,20 @@ void BoardWidget::paint_event(GUI::PaintEvent& event)
fill_color = Color::MidGray;
}
+ if (m_selected_pattern != nullptr) {
+ int y_offset = 0;
+ for (auto line : m_selected_pattern->pattern()) {
+ int x_offset = 0;
+ for (auto c : line) {
+ if (c == 'O' && (m_last_cell_hovered.row + y_offset) < m_board->rows()
+ && (m_last_cell_hovered.column + x_offset) < m_board->columns() && row == (m_last_cell_hovered.row + y_offset) && column == (m_last_cell_hovered.column + x_offset))
+ fill_color = Color::Green;
+ x_offset++;
+ }
+ y_offset++;
+ }
+ }
+
painter.fill_rect(cell_rect, fill_color);
if (cell_size > 4) {
painter.draw_rect(cell_rect, border_color);
@@ -138,12 +166,32 @@ void BoardWidget::paint_event(GUI::PaintEvent& event)
void BoardWidget::mousedown_event(GUI::MouseEvent& event)
{
- set_toggling_cells(true);
- auto row_and_column = get_row_and_column_for_point(event.x(), event.y());
- if (!row_and_column.has_value())
- return;
- auto [row, column] = row_and_column.value();
- toggle_cell(row, column);
+ if (event.button() == GUI::MouseButton::Left) {
+ set_toggling_cells(true);
+ auto row_and_column = get_row_and_column_for_point(event.x(), event.y());
+ if (!row_and_column.has_value())
+ return;
+ auto [row, column] = row_and_column.value();
+ if (m_selected_pattern == nullptr)
+ toggle_cell(row, column);
+ else
+ place_pattern(row, column);
+ }
+}
+
+void BoardWidget::context_menu_event(GUI::ContextMenuEvent& event)
+{
+ if (!m_context_menu) {
+ m_context_menu = GUI::Menu::construct();
+
+ auto& insert_pattern_menu = m_context_menu->add_submenu("&Insert Pattern");
+ for_each_pattern([&](auto& pattern) {
+ if (pattern.action())
+ insert_pattern_menu.add_action(*pattern.action());
+ });
+ }
+ if (!m_running)
+ m_context_menu->popup(event.screen_position());
}
void BoardWidget::mousemove_event(GUI::MouseEvent& event)
@@ -156,6 +204,11 @@ void BoardWidget::mousemove_event(GUI::MouseEvent& event)
if (m_last_cell_toggled.row != row || m_last_cell_toggled.column != column)
toggle_cell(row, column);
}
+ m_last_cell_hovered = { row, column };
+ if (m_selected_pattern != nullptr) {
+ if (!m_pattern_preview_timer->is_active())
+ m_pattern_preview_timer->start();
+ }
}
void BoardWidget::mouseup_event(GUI::MouseEvent&)
@@ -178,3 +231,156 @@ Optional<Board::RowAndColumn> BoardWidget::get_row_and_column_for_point(int x, i
.column = static_cast<size_t>((x - board_offset.width()) / cell_size),
} };
}
+
+void BoardWidget::place_pattern(size_t row, size_t column)
+{
+ int y_offset = 0;
+ for (auto line : m_selected_pattern->pattern()) {
+ int x_offset = 0;
+ for (auto c : line) {
+ if (c == 'O' && (row + y_offset) < m_board->rows() && (column + x_offset) < m_board->columns())
+ toggle_cell(row + y_offset, column + x_offset);
+ x_offset++;
+ }
+ y_offset++;
+ }
+ m_selected_pattern = nullptr;
+ if (m_pattern_preview_timer->is_active())
+ m_pattern_preview_timer->stop();
+}
+
+void BoardWidget::setup_patterns()
+{
+ auto add_pattern = [&](String name, NonnullOwnPtr<Pattern> pattern) {
+ auto action = GUI::Action::create(move(name), [this, pattern = pattern.ptr()](const GUI::Action&) {
+ on_pattern_selection(pattern);
+ });
+ pattern->set_action(action);
+ m_patterns.append(move(pattern));
+ };
+
+ Vector<String> blinker = {
+ "OOO"
+ };
+
+ Vector<String> toad = {
+ ".OOO",
+ "OOO."
+ };
+
+ Vector<String> glider = {
+ ".O.",
+ "..O",
+ "OOO",
+ };
+
+ Vector<String> lightweight_spaceship = {
+ ".OO..",
+ "OOOO.",
+ "OO.OO",
+ "..OO."
+ };
+
+ Vector<String> middleweight_spaceship = {
+ ".OOOOO",
+ "O....O",
+ ".....O",
+ "O...O.",
+ "..O..."
+ };
+
+ Vector<String> heavyweight_spaceship = {
+ "..OO...",
+ "O....O.",
+ "......O",
+ "O.....O",
+ ".OOOOOO"
+ };
+
+ Vector<String> infinite_1 = { "OOOOOOOO.OOOOO...OOO......OOOOOOO.OOOOO" };
+
+ Vector<String> infinite_2 = {
+ "......O.",
+ "....O.OO",
+ "....O.O.",
+ "....O...",
+ "..O.....",
+ "O.O....."
+ };
+
+ Vector<String> infinite_3 = {
+ "OOO.O",
+ "O....",
+ "...OO",
+ ".OO.O",
+ "O.O.O"
+ };
+
+ Vector<String> simkin_glider_gun = {
+ "OO.....OO........................",
+ "OO.....OO........................",
+ ".................................",
+ "....OO...........................",
+ "....OO...........................",
+ ".................................",
+ ".................................",
+ ".................................",
+ ".................................",
+ "......................OO.OO......",
+ ".....................O.....O.....",
+ ".....................O......O..OO",
+ ".....................OOO...O...OO",
+ "..........................O......",
+ ".................................",
+ ".................................",
+ ".................................",
+ "....................OO...........",
+ "....................O............",
+ ".....................OOO.........",
+ ".......................O........."
+ };
+ Vector<String> gosper_glider_gun = {
+ "........................O...........",
+ "......................O.O...........",
+ "............OO......OO............OO",
+ "...........O...O....OO............OO",
+ "OO........O.....O...OO..............",
+ "OO........O...O.OO....O.O...........",
+ "..........O.....O.......O...........",
+ "...........O...O....................",
+ "............OO......................"
+ };
+
+ Vector<String> r_pentomino = {
+ ".OO",
+ "OO.",
+ ".O."
+ };
+
+ Vector<String> diehard = {
+ "......O.",
+ "OO......",
+ ".O...OOO"
+ };
+
+ Vector<String> acorn = {
+ ".O.....",
+ "...O...",
+ "OO..OOO"
+ };
+
+ add_pattern("Blinker", make<Pattern>(move(blinker)));
+ add_pattern("Toad", make<Pattern>(move(toad)));
+ add_pattern("Glider", make<Pattern>(move(glider)));
+ add_pattern("Lightweight Spaceship", make<Pattern>(move(lightweight_spaceship)));
+ add_pattern("Middleweight Spaceship", make<Pattern>(move(middleweight_spaceship)));
+ add_pattern("Heavyweight Spaceship", make<Pattern>(move(heavyweight_spaceship)));
+ add_pattern("Infinite 1", make<Pattern>(move(infinite_1)));
+ add_pattern("Infinite 2", make<Pattern>(move(infinite_2)));
+ add_pattern("Infinite 3", make<Pattern>(move(infinite_3)));
+ add_pattern("R-Pentomino", make<Pattern>(move(r_pentomino)));
+ add_pattern("Diehard", make<Pattern>(move(diehard)));
+ add_pattern("Acorn", make<Pattern>(move(acorn)));
+ add_pattern("Simkin's Glider Gun", make<Pattern>(move(simkin_glider_gun)));
+ add_pattern("Gosper's Glider Gun", make<Pattern>(move(gosper_glider_gun)));
+}
diff --git a/Userland/Games/GameOfLife/BoardWidget.h b/Userland/Games/GameOfLife/BoardWidget.h
index 7933c96e8f..b72b4b1088 100644
--- a/Userland/Games/GameOfLife/BoardWidget.h
+++ b/Userland/Games/GameOfLife/BoardWidget.h
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2021, Andres Crucitti <dasc495@gmail.com>
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2021, Ryan Wilson <ryan@rdwilson.xyz>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -8,11 +9,13 @@
#pragma once
#include "Board.h"
+#include "Pattern.h"
#include <AK/Function.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Optional.h>
#include <AK/RefPtr.h>
#include <LibCore/Timer.h>
+#include <LibGUI/Menu.h>
#include <LibGUI/Widget.h>
class BoardWidget final : public GUI::Widget {
@@ -23,6 +26,7 @@ public:
virtual void mousemove_event(GUI::MouseEvent&) override;
virtual void mouseup_event(GUI::MouseEvent&) override;
virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void context_menu_event(GUI::ContextMenuEvent&) override;
void set_toggling_cells(bool toggling)
{
@@ -46,6 +50,15 @@ public:
bool is_running() const { return m_running; }
void set_running(bool r);
+ Pattern* selected_pattern() { return m_selected_pattern; }
+ Function<void(Pattern*)> on_pattern_selection;
+ template<typename Callback>
+ void for_each_pattern(Callback callback)
+ {
+ for (auto& pattern : m_patterns)
+ callback(pattern);
+ }
+
void run_generation();
int running_timer_interval() const { return m_running_timer_interval; }
@@ -57,14 +70,24 @@ public:
private:
BoardWidget(size_t rows, size_t columns);
+ void setup_patterns();
+ void place_pattern(size_t row, size_t column);
bool m_toggling_cells { false };
Board::RowAndColumn m_last_cell_toggled {};
+ Board::RowAndColumn m_last_cell_hovered {};
+ Pattern* m_selected_pattern { nullptr };
+ NonnullOwnPtrVector<Pattern> m_patterns;
NonnullOwnPtr<Board> m_board;
bool m_running { false };
int m_running_timer_interval { 500 };
+ int m_running_pattern_preview_timer_interval { 100 };
+
+ RefPtr<GUI::Menu> m_context_menu;
+
RefPtr<Core::Timer> m_timer;
+ RefPtr<Core::Timer> m_pattern_preview_timer;
};
diff --git a/Userland/Games/GameOfLife/CMakeLists.txt b/Userland/Games/GameOfLife/CMakeLists.txt
index 7ea612fd5d..225e4f1b8a 100644
--- a/Userland/Games/GameOfLife/CMakeLists.txt
+++ b/Userland/Games/GameOfLife/CMakeLists.txt
@@ -1,10 +1,11 @@
compile_gml(GameOfLife.gml GameOfLifeGML.h game_of_life_gml)
set(SOURCES
+ main.cpp
Board.cpp
BoardWidget.cpp
GameOfLifeGML.h
- main.cpp
+ Pattern.cpp
)
serenity_app(GameOfLife ICON app-gameoflife)
diff --git a/Userland/Games/GameOfLife/Pattern.cpp b/Userland/Games/GameOfLife/Pattern.cpp
new file mode 100644
index 0000000000..35376c6375
--- /dev/null
+++ b/Userland/Games/GameOfLife/Pattern.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021, Ryan Wilson <ryan@rdwilson.xyz>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "Pattern.h"
+#include <AK/String.h>
+#include <AK/StringBuilder.h>
+#include <AK/Vector.h>
+#include <LibGUI/Action.h>
+#include <stdio.h>
+
+Pattern::Pattern(Vector<String> pattern)
+{
+ m_pattern = move(pattern);
+}
+
+Pattern::~Pattern()
+{
+}
+
+void Pattern::set_action(GUI::Action* action)
+{
+ m_action = action;
+}
+
+void Pattern::rotate_clockwise()
+{
+ Vector<String> rotated;
+ for (size_t i = 0; i < m_pattern.first().length(); i++) {
+ StringBuilder builder;
+ for (int j = m_pattern.size() - 1; j >= 0; j--) {
+ builder.append(m_pattern.at(j).substring(i, 1));
+ }
+ rotated.append(builder.to_string());
+ }
+ m_pattern = move(rotated);
+}
diff --git a/Userland/Games/GameOfLife/Pattern.h b/Userland/Games/GameOfLife/Pattern.h
new file mode 100644
index 0000000000..9fa3ce1c8b
--- /dev/null
+++ b/Userland/Games/GameOfLife/Pattern.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2021, Ryan Wilson <ryan@rdwilson.xyz>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "Board.h"
+#include <AK/Vector.h>
+#include <LibGUI/Event.h>
+#include <LibGUI/Forward.h>
+
+class Pattern {
+public:
+ Pattern(Vector<String>);
+ virtual ~Pattern();
+ Vector<String> pattern() { return m_pattern; };
+ GUI::Action* action() { return m_action; }
+ void set_action(GUI::Action*);
+ void rotate_clockwise();
+
+private:
+ RefPtr<GUI::Action> m_action;
+ Vector<String> m_pattern;
+};
diff --git a/Userland/Games/GameOfLife/main.cpp b/Userland/Games/GameOfLife/main.cpp
index aebb038bcd..bbf0846b7d 100644
--- a/Userland/Games/GameOfLife/main.cpp
+++ b/Userland/Games/GameOfLife/main.cpp
@@ -15,6 +15,7 @@
#include <LibGUI/SpinBox.h>
#include <LibGUI/Statusbar.h>
#include <LibGUI/Toolbar.h>
+#include <LibGUI/ToolbarContainer.h>
#include <LibGUI/Window.h>
const char* click_tip = "Tip: click the board to toggle individual cells, or click+drag to toggle multiple cells";
@@ -102,6 +103,12 @@ int main(int argc, char** argv)
});
main_toolbar.add_action(randomize_cells_action);
+ auto rotate_pattern_action = GUI::Action::create("&Rotate pattern", { 0, Key_R }, Gfx::Bitmap::load_from_file("/res/icons/16x16/redo.png"), [&](auto&) {
+ if (board_widget.selected_pattern() != nullptr)
+ board_widget.selected_pattern()->rotate_clockwise();
+ });
+ main_toolbar.add_action(rotate_pattern_action);
+
auto menubar = GUI::Menubar::construct();
auto& game_menu = menubar->add_menu("&Game");