mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 00:07:36 +00:00
GameOfLife: Add choosable patterns
This commit is contained in:
parent
b808815e57
commit
1965d60aeb
6 changed files with 309 additions and 7 deletions
|
@ -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)
|
||||
{
|
||||
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)));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
|
39
Userland/Games/GameOfLife/Pattern.cpp
Normal file
39
Userland/Games/GameOfLife/Pattern.cpp
Normal file
|
@ -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);
|
||||
}
|
26
Userland/Games/GameOfLife/Pattern.h
Normal file
26
Userland/Games/GameOfLife/Pattern.h
Normal file
|
@ -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;
|
||||
};
|
|
@ -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");
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue