mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 15:40:19 +00:00
2048: Evil AI logic: select new tile in the worst position
Not very smart: the worst position is defined such that after the player's next move the fewest tiles remain empty.
This commit is contained in:
parent
4434e900af
commit
a93d0fe8c2
Notes:
sideshowbarker
2024-07-18 17:52:33 +09:00
Author: https://github.com/dmitrii-ubskii Commit: https://github.com/SerenityOS/serenity/commit/a93d0fe8c27 Pull-request: https://github.com/SerenityOS/serenity/pull/7146 Reviewed-by: https://github.com/bugaevc ✅ Reviewed-by: https://github.com/linusg
3 changed files with 83 additions and 7 deletions
|
@ -5,11 +5,14 @@
|
|||
*/
|
||||
|
||||
#include "Game.h"
|
||||
#include <AK/Array.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AK/String.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
Game::Game(size_t grid_size, size_t target_tile)
|
||||
Game::Game(size_t grid_size, size_t target_tile, bool evil_ai)
|
||||
: m_grid_size(grid_size)
|
||||
, m_evil_ai(evil_ai)
|
||||
{
|
||||
if (target_tile == 0)
|
||||
m_target_tile = 2048;
|
||||
|
@ -25,8 +28,8 @@ Game::Game(size_t grid_size, size_t target_tile)
|
|||
row.append(0);
|
||||
}
|
||||
|
||||
add_random_tile();
|
||||
add_random_tile();
|
||||
add_tile();
|
||||
add_tile();
|
||||
}
|
||||
|
||||
void Game::add_random_tile()
|
||||
|
@ -161,6 +164,16 @@ static bool is_stalled(const Game::Board& board)
|
|||
return true;
|
||||
}
|
||||
|
||||
static size_t get_number_of_free_cells(const Game::Board& board)
|
||||
{
|
||||
size_t accumulator = 0;
|
||||
for (auto& row : board) {
|
||||
for (auto& cell : row)
|
||||
accumulator += cell == 0;
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
bool Game::slide_tiles(Direction direction)
|
||||
{
|
||||
size_t successful_merge_score = 0;
|
||||
|
@ -195,7 +208,7 @@ Game::MoveOutcome Game::attempt_move(Direction direction)
|
|||
bool moved = slide_tiles(direction);
|
||||
if (moved) {
|
||||
m_turns++;
|
||||
add_random_tile();
|
||||
add_tile();
|
||||
}
|
||||
|
||||
if (is_complete(m_board, m_target_tile))
|
||||
|
@ -207,6 +220,57 @@ Game::MoveOutcome Game::attempt_move(Direction direction)
|
|||
return MoveOutcome::InvalidMove;
|
||||
}
|
||||
|
||||
void Game::add_evil_tile()
|
||||
{
|
||||
size_t worst_row = 0;
|
||||
size_t worst_column = 0;
|
||||
u32 worst_value = 2;
|
||||
|
||||
size_t most_free_cells = NumericLimits<size_t>::max();
|
||||
|
||||
for (size_t row = 0; row < m_grid_size; row++) {
|
||||
for (size_t column = 0; column < m_grid_size; column++) {
|
||||
if (m_board[row][column] != 0)
|
||||
continue;
|
||||
|
||||
for (u32 value : Array { 2, 4 }) {
|
||||
Game saved_state = *this;
|
||||
saved_state.m_board[row][column] = value;
|
||||
|
||||
if (is_stalled(saved_state.m_board)) {
|
||||
// We can stall the board now, instant game over.
|
||||
worst_row = row;
|
||||
worst_column = column;
|
||||
worst_value = value;
|
||||
|
||||
goto found_worst_tile;
|
||||
}
|
||||
|
||||
// This is the best outcome the player can achieve in one move.
|
||||
// We want this to be as low as possible.
|
||||
size_t best_outcome = 0;
|
||||
for (auto direction : Array { Direction::Down, Direction::Left, Direction::Right, Direction::Up }) {
|
||||
Game moved_state = saved_state;
|
||||
bool moved = moved_state.slide_tiles(direction);
|
||||
if (!moved) // invalid move
|
||||
continue;
|
||||
best_outcome = max(best_outcome, get_number_of_free_cells(moved_state.board()));
|
||||
}
|
||||
|
||||
if (best_outcome < most_free_cells) {
|
||||
worst_row = row;
|
||||
worst_column = column;
|
||||
worst_value = value;
|
||||
|
||||
most_free_cells = best_outcome;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
found_worst_tile:
|
||||
m_board[worst_row][worst_column] = worst_value;
|
||||
}
|
||||
|
||||
u32 Game::largest_tile() const
|
||||
{
|
||||
u32 tile = 0;
|
||||
|
|
|
@ -10,8 +10,9 @@
|
|||
|
||||
class Game final {
|
||||
public:
|
||||
Game(size_t board_size, size_t target_tile = 0);
|
||||
Game(size_t grid_size, size_t target_tile, bool evil_ai);
|
||||
Game(const Game&) = default;
|
||||
Game& operator=(const Game&) = default;
|
||||
|
||||
enum class MoveOutcome {
|
||||
OK,
|
||||
|
@ -49,11 +50,22 @@ public:
|
|||
private:
|
||||
bool slide_tiles(Direction);
|
||||
|
||||
void add_tile()
|
||||
{
|
||||
if (m_evil_ai)
|
||||
add_evil_tile();
|
||||
else
|
||||
add_random_tile();
|
||||
}
|
||||
|
||||
void add_random_tile();
|
||||
void add_evil_tile();
|
||||
|
||||
size_t m_grid_size { 0 };
|
||||
u32 m_target_tile { 0 };
|
||||
|
||||
bool m_evil_ai { false };
|
||||
|
||||
Board m_board;
|
||||
size_t m_score { 0 };
|
||||
size_t m_turns { 0 };
|
||||
|
|
|
@ -76,7 +76,7 @@ int main(int argc, char** argv)
|
|||
main_widget.set_layout<GUI::VerticalBoxLayout>();
|
||||
main_widget.set_fill_with_background_color(true);
|
||||
|
||||
Game game { board_size, target_tile };
|
||||
Game game { board_size, target_tile, evil_ai };
|
||||
|
||||
auto& board_view = main_widget.add<BoardView>(&game.board());
|
||||
board_view.set_focus(true);
|
||||
|
@ -123,7 +123,7 @@ int main(int argc, char** argv)
|
|||
undo_stack.clear();
|
||||
redo_stack.clear();
|
||||
|
||||
game = Game(board_size, target_tile);
|
||||
game = Game(board_size, target_tile, evil_ai);
|
||||
|
||||
// This ensures that the sizes are correct.
|
||||
board_view.set_board(nullptr);
|
||||
|
|
Loading…
Reference in a new issue