From a93d0fe8c27f0b03f9bd3243974edd7f248c3f27 Mon Sep 17 00:00:00 2001 From: Dmitrii Ubskii Date: Sat, 15 May 2021 17:37:29 +0300 Subject: [PATCH] 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. --- Userland/Games/2048/Game.cpp | 72 ++++++++++++++++++++++++++++++++++-- Userland/Games/2048/Game.h | 14 ++++++- Userland/Games/2048/main.cpp | 4 +- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/Userland/Games/2048/Game.cpp b/Userland/Games/2048/Game.cpp index c196dc9d8c..ac68441930 100644 --- a/Userland/Games/2048/Game.cpp +++ b/Userland/Games/2048/Game.cpp @@ -5,11 +5,14 @@ */ #include "Game.h" +#include +#include #include #include -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::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; diff --git a/Userland/Games/2048/Game.h b/Userland/Games/2048/Game.h index 1e36afbccb..0c5ee5f039 100644 --- a/Userland/Games/2048/Game.h +++ b/Userland/Games/2048/Game.h @@ -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 }; diff --git a/Userland/Games/2048/main.cpp b/Userland/Games/2048/main.cpp index 97a3440a0e..db75e0aee5 100644 --- a/Userland/Games/2048/main.cpp +++ b/Userland/Games/2048/main.cpp @@ -76,7 +76,7 @@ int main(int argc, char** argv) main_widget.set_layout(); 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(&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);