diff options
author | Ryan Wilson <ryan@rdwilson.xyz> | 2021-05-22 05:54:58 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-22 09:54:58 +0100 |
commit | 1965d60aebf3ee9cc4631b277eaf37bc8edd92f2 (patch) | |
tree | f3ac07ab9dde088205ec08e1a1d540224b7aa86c | |
parent | b808815e57fe680ec2d98ac2dc5bccd610e871ca (diff) | |
download | serenity-1965d60aebf3ee9cc4631b277eaf37bc8edd92f2.zip |
GameOfLife: Add choosable patterns
-rw-r--r-- | Userland/Games/GameOfLife/BoardWidget.cpp | 218 | ||||
-rw-r--r-- | Userland/Games/GameOfLife/BoardWidget.h | 23 | ||||
-rw-r--r-- | Userland/Games/GameOfLife/CMakeLists.txt | 3 | ||||
-rw-r--r-- | Userland/Games/GameOfLife/Pattern.cpp | 39 | ||||
-rw-r--r-- | Userland/Games/GameOfLife/Pattern.h | 26 | ||||
-rw-r--r-- | Userland/Games/GameOfLife/main.cpp | 7 |
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"); |