2020-08-08 22:10:41 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2020, the SerenityOS developers.
|
|
|
|
*
|
2021-04-22 08:24:48 +00:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-08-08 22:10:41 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
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 13:01:25 +00:00
|
|
|
#include <AK/Vector.h>
|
2020-08-08 22:10:41 +00: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 13:01:25 +00:00
|
|
|
class Game final {
|
2020-08-08 22:10:41 +00:00
|
|
|
public:
|
2021-05-15 14:37:29 +00:00
|
|
|
Game(size_t grid_size, size_t target_tile, bool evil_ai);
|
2021-06-11 12:39:23 +00:00
|
|
|
Game(Game const&) = default;
|
|
|
|
Game& operator=(Game const&) = default;
|
2020-08-08 22:10:41 +00: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 13:01:25 +00:00
|
|
|
enum class MoveOutcome {
|
|
|
|
OK,
|
|
|
|
InvalidMove,
|
|
|
|
GameOver,
|
|
|
|
Won,
|
|
|
|
};
|
2020-08-08 22:10:41 +00: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 13:01:25 +00:00
|
|
|
enum class Direction {
|
|
|
|
Up,
|
|
|
|
Down,
|
|
|
|
Left,
|
|
|
|
Right,
|
2020-08-08 22:10:41 +00: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 13:01:25 +00:00
|
|
|
MoveOutcome attempt_move(Direction);
|
2020-08-08 22:10:41 +00: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 13:01:25 +00:00
|
|
|
size_t score() const { return m_score; }
|
|
|
|
size_t turns() const { return m_turns; }
|
2020-08-19 16:17:17 +00:00
|
|
|
u32 target_tile() const { return m_target_tile; }
|
|
|
|
u32 largest_tile() const;
|
2021-10-04 17:57:13 +00:00
|
|
|
void set_want_to_continue() { m_want_to_continue = true; }
|
2021-06-11 15:21:16 +00:00
|
|
|
class Board {
|
|
|
|
public:
|
2021-06-11 12:26:56 +00:00
|
|
|
using Row = Vector<u32>;
|
|
|
|
using Tiles = Vector<Row>;
|
|
|
|
|
2021-06-11 15:21:16 +00:00
|
|
|
Tiles const& tiles() const { return m_tiles; }
|
|
|
|
|
|
|
|
bool is_stalled();
|
2021-06-11 12:26:56 +00:00
|
|
|
|
2021-06-11 12:28:42 +00:00
|
|
|
struct Position {
|
|
|
|
size_t row;
|
|
|
|
size_t column;
|
|
|
|
|
|
|
|
bool operator==(Position const& other) const
|
|
|
|
{
|
|
|
|
return row == other.row && column == other.column;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-06-11 12:26:56 +00:00
|
|
|
void add_tile(size_t row, size_t column, u32 value)
|
|
|
|
{
|
2021-06-11 15:21:16 +00:00
|
|
|
m_tiles[row][column] = value;
|
|
|
|
m_last_added_position = Position { row, column };
|
2021-06-11 12:26:56 +00:00
|
|
|
}
|
2021-06-11 15:21:16 +00:00
|
|
|
Position const& last_added_position() const { return m_last_added_position; }
|
|
|
|
|
|
|
|
struct SlideResult {
|
|
|
|
bool has_moved;
|
|
|
|
size_t score_delta;
|
|
|
|
};
|
|
|
|
SlideResult slide_tiles(Direction);
|
|
|
|
|
2021-06-11 19:50:54 +00:00
|
|
|
struct SlidingTile {
|
|
|
|
size_t row_from;
|
|
|
|
size_t column_from;
|
|
|
|
u32 value_from;
|
|
|
|
|
|
|
|
size_t row_to;
|
|
|
|
size_t column_to;
|
|
|
|
u32 value_to;
|
|
|
|
};
|
|
|
|
Vector<SlidingTile> const& sliding_tiles() const { return m_sliding_tiles; }
|
|
|
|
|
2021-06-11 15:21:16 +00:00
|
|
|
private:
|
|
|
|
void reverse();
|
|
|
|
void transpose();
|
2021-06-11 12:28:42 +00:00
|
|
|
|
2021-06-11 15:21:16 +00:00
|
|
|
size_t slide_row(size_t row_index);
|
|
|
|
size_t slide_left();
|
|
|
|
|
|
|
|
friend Game;
|
|
|
|
|
|
|
|
Tiles m_tiles;
|
|
|
|
|
|
|
|
Position m_last_added_position { 0, 0 };
|
2021-06-11 19:50:54 +00:00
|
|
|
Vector<SlidingTile> m_sliding_tiles;
|
2021-06-11 12:26:56 +00:00
|
|
|
};
|
2020-08-08 22:10:41 +00:00
|
|
|
|
2021-06-11 12:39:23 +00:00
|
|
|
Board const& board() const { return m_board; }
|
2020-08-08 22:10:41 +00:00
|
|
|
|
2020-08-19 16:17:17 +00:00
|
|
|
static size_t max_power_for_board(size_t size)
|
|
|
|
{
|
|
|
|
if (size >= 6)
|
|
|
|
return 31;
|
|
|
|
|
|
|
|
return size * size + 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 13:01:25 +00:00
|
|
|
private:
|
2021-05-15 14:37:29 +00:00
|
|
|
void add_tile()
|
|
|
|
{
|
|
|
|
if (m_evil_ai)
|
|
|
|
add_evil_tile();
|
|
|
|
else
|
|
|
|
add_random_tile();
|
|
|
|
}
|
|
|
|
|
2020-08-18 13:30:25 +00:00
|
|
|
void add_random_tile();
|
2021-05-15 14:37:29 +00:00
|
|
|
void add_evil_tile();
|
2020-08-18 11:02:09 +00:00
|
|
|
|
2020-08-19 13:54:03 +00:00
|
|
|
size_t m_grid_size { 0 };
|
2020-08-19 16:17:17 +00:00
|
|
|
u32 m_target_tile { 0 };
|
2020-08-18 11:02:09 +00:00
|
|
|
|
2021-05-15 14:37:29 +00:00
|
|
|
bool m_evil_ai { false };
|
2021-10-04 17:57:13 +00:00
|
|
|
bool m_want_to_continue { false };
|
2021-05-15 14:37:29 +00: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 13:01:25 +00:00
|
|
|
Board m_board;
|
|
|
|
size_t m_score { 0 };
|
|
|
|
size_t m_turns { 0 };
|
2020-08-08 22:10:41 +00:00
|
|
|
};
|