2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2020, the SerenityOS developers.
|
|
|
|
|
*
|
2021-04-22 05:24:48 -03:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "BoardView.h"
|
|
|
|
|
#include <LibGUI/Painter.h>
|
|
|
|
|
#include <LibGfx/Font.h>
|
2020-10-31 06:18:49 -03:00
|
|
|
#include <LibGfx/FontDatabase.h>
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
#include <LibGfx/Palette.h>
|
|
|
|
|
|
2021-06-11 09:39:23 -03:00
|
|
|
BoardView::BoardView(Game::Board const* board)
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
: m_board(board)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BoardView::~BoardView()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-11 09:39:23 -03:00
|
|
|
void BoardView::set_board(Game::Board const* board)
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
{
|
2021-06-11 09:28:42 -03:00
|
|
|
if (has_timer())
|
|
|
|
|
stop_timer();
|
|
|
|
|
|
2021-06-11 16:50:54 -03:00
|
|
|
slide_animation_frame = 0;
|
2021-06-11 09:28:42 -03:00
|
|
|
pop_in_animation_frame = 0;
|
|
|
|
|
start_timer(frame_duration_ms);
|
|
|
|
|
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
if (m_board == board)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-08-19 13:17:17 -03:00
|
|
|
if (!board) {
|
|
|
|
|
m_board = nullptr;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-11 12:21:16 -03:00
|
|
|
bool must_resize = !m_board || m_board->tiles().size() != board->tiles().size();
|
2020-08-19 13:17:17 -03:00
|
|
|
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
m_board = board;
|
2020-08-19 13:17:17 -03:00
|
|
|
|
|
|
|
|
if (must_resize)
|
|
|
|
|
resize();
|
|
|
|
|
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BoardView::pick_font()
|
|
|
|
|
{
|
2020-10-25 15:28:06 -03:00
|
|
|
String best_font_name;
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
int best_font_size = -1;
|
2020-10-31 06:18:49 -03:00
|
|
|
auto& font_database = Gfx::FontDatabase::the();
|
2021-06-11 09:39:23 -03:00
|
|
|
font_database.for_each_font([&](Gfx::Font const& font) {
|
2020-10-25 15:28:06 -03:00
|
|
|
if (font.family() != "Liza" || font.weight() != 700)
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
return;
|
2020-10-25 15:28:06 -03:00
|
|
|
auto size = font.glyph_height();
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
if (size * 2 <= m_cell_size && size > best_font_size) {
|
2020-10-25 15:28:06 -03:00
|
|
|
best_font_name = font.qualified_name();
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
best_font_size = size;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto font = font_database.get_by_name(best_font_name);
|
|
|
|
|
set_font(font);
|
2021-06-11 09:28:42 -03:00
|
|
|
|
|
|
|
|
m_min_cell_size = best_font_size;
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t BoardView::rows() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_board)
|
|
|
|
|
return 0;
|
2021-06-11 12:21:16 -03:00
|
|
|
return m_board->tiles().size();
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t BoardView::columns() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_board)
|
|
|
|
|
return 0;
|
2021-06-11 12:21:16 -03:00
|
|
|
if (m_board->tiles().is_empty())
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
return 0;
|
2021-06-11 12:21:16 -03:00
|
|
|
return m_board->tiles()[0].size();
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BoardView::resize_event(GUI::ResizeEvent&)
|
2020-08-19 13:17:17 -03:00
|
|
|
{
|
|
|
|
|
resize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BoardView::resize()
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
{
|
|
|
|
|
constexpr float padding_ratio = 7;
|
|
|
|
|
m_padding = min(
|
|
|
|
|
width() / (columns() * (padding_ratio + 1) + 1),
|
2020-08-18 10:20:43 -03:00
|
|
|
height() / (rows() * (padding_ratio + 1) + 1));
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
m_cell_size = m_padding * padding_ratio;
|
|
|
|
|
|
|
|
|
|
pick_font();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BoardView::keydown_event(GUI::KeyEvent& event)
|
|
|
|
|
{
|
|
|
|
|
if (!on_move)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
switch (event.key()) {
|
|
|
|
|
case KeyCode::Key_A:
|
|
|
|
|
case KeyCode::Key_Left:
|
|
|
|
|
on_move(Game::Direction::Left);
|
|
|
|
|
break;
|
|
|
|
|
case KeyCode::Key_D:
|
|
|
|
|
case KeyCode::Key_Right:
|
|
|
|
|
on_move(Game::Direction::Right);
|
|
|
|
|
break;
|
|
|
|
|
case KeyCode::Key_W:
|
|
|
|
|
case KeyCode::Key_Up:
|
|
|
|
|
on_move(Game::Direction::Up);
|
|
|
|
|
break;
|
|
|
|
|
case KeyCode::Key_S:
|
|
|
|
|
case KeyCode::Key_Down:
|
|
|
|
|
on_move(Game::Direction::Down);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gfx::Color BoardView::background_color_for_cell(u32 value)
|
|
|
|
|
{
|
|
|
|
|
switch (value) {
|
|
|
|
|
case 0:
|
|
|
|
|
return Color::from_rgb(0xcdc1b4);
|
|
|
|
|
case 2:
|
|
|
|
|
return Color::from_rgb(0xeee4da);
|
|
|
|
|
case 4:
|
|
|
|
|
return Color::from_rgb(0xede0c8);
|
|
|
|
|
case 8:
|
|
|
|
|
return Color::from_rgb(0xf2b179);
|
|
|
|
|
case 16:
|
|
|
|
|
return Color::from_rgb(0xf59563);
|
|
|
|
|
case 32:
|
|
|
|
|
return Color::from_rgb(0xf67c5f);
|
|
|
|
|
case 64:
|
|
|
|
|
return Color::from_rgb(0xf65e3b);
|
|
|
|
|
case 128:
|
|
|
|
|
return Color::from_rgb(0xedcf72);
|
|
|
|
|
case 256:
|
|
|
|
|
return Color::from_rgb(0xedcc61);
|
|
|
|
|
case 512:
|
|
|
|
|
return Color::from_rgb(0xedc850);
|
|
|
|
|
case 1024:
|
|
|
|
|
return Color::from_rgb(0xedc53f);
|
|
|
|
|
case 2048:
|
|
|
|
|
return Color::from_rgb(0xedc22e);
|
|
|
|
|
default:
|
2021-02-23 16:42:32 -03:00
|
|
|
VERIFY(value > 2048);
|
2020-08-19 13:17:17 -03:00
|
|
|
return Color::from_rgb(0x3c3a32);
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gfx::Color BoardView::text_color_for_cell(u32 value)
|
|
|
|
|
{
|
|
|
|
|
if (value <= 4)
|
|
|
|
|
return Color::from_rgb(0x776e65);
|
|
|
|
|
return Color::from_rgb(0xf9f6f2);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-11 09:28:42 -03:00
|
|
|
void BoardView::timer_event(Core::TimerEvent&)
|
|
|
|
|
{
|
2021-06-11 16:50:54 -03:00
|
|
|
if (slide_animation_frame < animation_duration) {
|
|
|
|
|
slide_animation_frame++;
|
|
|
|
|
update();
|
|
|
|
|
} else if (pop_in_animation_frame < animation_duration) {
|
2021-06-11 09:28:42 -03:00
|
|
|
pop_in_animation_frame++;
|
|
|
|
|
update();
|
|
|
|
|
if (pop_in_animation_frame == animation_duration)
|
|
|
|
|
stop_timer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-04 18:51:00 -03:00
|
|
|
void BoardView::paint_event(GUI::PaintEvent& event)
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
{
|
2021-04-04 18:51:00 -03:00
|
|
|
Frame::paint_event(event);
|
|
|
|
|
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
Color background_color = Color::from_rgb(0xbbada0);
|
|
|
|
|
|
|
|
|
|
GUI::Painter painter(*this);
|
2021-04-04 18:51:00 -03:00
|
|
|
painter.add_clip_rect(event.rect());
|
|
|
|
|
painter.add_clip_rect(frame_inner_rect());
|
|
|
|
|
painter.translate(frame_thickness(), frame_thickness());
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
|
|
|
|
|
if (!m_board) {
|
|
|
|
|
painter.fill_rect(rect(), background_color);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-06-11 12:21:16 -03:00
|
|
|
auto& tiles = m_board->tiles();
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
|
|
|
|
|
Gfx::IntRect field_rect {
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
static_cast<int>(m_padding + (m_cell_size + m_padding) * columns()),
|
|
|
|
|
static_cast<int>(m_padding + (m_cell_size + m_padding) * rows())
|
|
|
|
|
};
|
2020-08-18 10:20:43 -03:00
|
|
|
field_rect.center_within(rect());
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
painter.fill_rect(field_rect, background_color);
|
|
|
|
|
|
2021-06-11 16:50:54 -03:00
|
|
|
auto tile_center = [&](size_t row, size_t column) {
|
|
|
|
|
return Gfx::IntPoint {
|
|
|
|
|
field_rect.x() + m_padding + (m_cell_size + m_padding) * column + m_cell_size / 2,
|
|
|
|
|
field_rect.y() + m_padding + (m_cell_size + m_padding) * row + m_cell_size / 2,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (slide_animation_frame < animation_duration) {
|
|
|
|
|
// background
|
|
|
|
|
for (size_t column = 0; column < columns(); ++column) {
|
|
|
|
|
for (size_t row = 0; row < rows(); ++row) {
|
|
|
|
|
auto center = tile_center(row, column);
|
|
|
|
|
auto tile_size = Gfx::IntSize { m_cell_size, m_cell_size };
|
|
|
|
|
auto rect = Gfx::IntRect::centered_on(center, tile_size);
|
|
|
|
|
painter.fill_rect(rect, background_color_for_cell(0));
|
2021-06-11 09:28:42 -03:00
|
|
|
}
|
2021-06-11 16:50:54 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto& sliding_tile : m_board->sliding_tiles()) {
|
|
|
|
|
auto center_from = tile_center(sliding_tile.row_from, sliding_tile.column_from);
|
|
|
|
|
auto center_to = tile_center(sliding_tile.row_to, sliding_tile.column_to);
|
|
|
|
|
auto offset = Gfx::FloatPoint(center_to - center_from);
|
|
|
|
|
auto center = center_from + Gfx::IntPoint(offset * (slide_animation_frame / (float)animation_duration));
|
|
|
|
|
|
|
|
|
|
auto tile_size = Gfx::IntSize { m_cell_size, m_cell_size };
|
2021-06-11 09:28:42 -03:00
|
|
|
auto rect = Gfx::IntRect::centered_on(center, tile_size);
|
2021-06-11 16:50:54 -03:00
|
|
|
|
|
|
|
|
painter.fill_rect(rect, background_color_for_cell(sliding_tile.value_from));
|
|
|
|
|
painter.draw_text(rect, String::number(sliding_tile.value_from), font(), Gfx::TextAlignment::Center, text_color_for_cell(sliding_tile.value_from));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (size_t column = 0; column < columns(); ++column) {
|
|
|
|
|
for (size_t row = 0; row < rows(); ++row) {
|
|
|
|
|
auto center = tile_center(row, column);
|
|
|
|
|
auto tile_size = Gfx::IntSize { m_cell_size, m_cell_size };
|
|
|
|
|
if (pop_in_animation_frame < animation_duration && Game::Board::Position { row, column } == m_board->last_added_position()) {
|
|
|
|
|
float pop_in_size = m_min_cell_size + (m_cell_size - m_min_cell_size) * (pop_in_animation_frame / (float)animation_duration);
|
|
|
|
|
tile_size = Gfx::IntSize { pop_in_size, pop_in_size };
|
|
|
|
|
}
|
|
|
|
|
auto rect = Gfx::IntRect::centered_on(center, tile_size);
|
|
|
|
|
auto entry = tiles[row][column];
|
|
|
|
|
painter.fill_rect(rect, background_color_for_cell(entry));
|
|
|
|
|
if (entry > 0)
|
|
|
|
|
painter.draw_text(rect, String::number(entry), font(), Gfx::TextAlignment::Center, text_color_for_cell(entry));
|
|
|
|
|
}
|
2048: Separate game logic from the view :^)
Look Ali, it's simple:
* The *model* (in many cases, an instance of GUI::Model, but it doesn't have to
be) should implement the "business logic" (in this case, game logic) and
should not concern itself with how the data/state is displayed to the user.
* The *view*, conversely, should interact with the user (display data/state,
accept input) and should not concern itself with the logic. As an example, a
GUI::Button can display some text and accept clicks -- it doesn't know or care
what that text *means*, or how that click affects the app state. All it does
is it gets its text from *somebody* and notifies *somebody* of clicks.
* The *controller* connects the model to the view, and acts as "glue" between
them.
You could connect *several different* views to one model (see FileManager), or
use identical views with different models (e.g. a table view can display pretty
much anything, depending on what model you connect to it).
In this case, the model is the Game class, which maintains a board and
implements the rules of 2048, including tracking the score. It does not display
anything, and it does not concern itself with undo management. The view is the
BoardView class, which displays a board and accepts keyboard input, but doesn't
know how exactly the tiles move or merge -- all it gets is a board state, ready
to be displayed. The controller is our main(), which connects the two classes
and bridges between their APIs. It also implements undo management, by basically
making straight-up copies of the game.
Isn't this lovely?
2020-08-18 10:01:25 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|