mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 05:37:34 +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
3 changed files with 83 additions and 7 deletions
|
@ -5,11 +5,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
|
#include <AK/Array.h>
|
||||||
|
#include <AK/NumericLimits.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <stdlib.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_grid_size(grid_size)
|
||||||
|
, m_evil_ai(evil_ai)
|
||||||
{
|
{
|
||||||
if (target_tile == 0)
|
if (target_tile == 0)
|
||||||
m_target_tile = 2048;
|
m_target_tile = 2048;
|
||||||
|
@ -25,8 +28,8 @@ Game::Game(size_t grid_size, size_t target_tile)
|
||||||
row.append(0);
|
row.append(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_random_tile();
|
add_tile();
|
||||||
add_random_tile();
|
add_tile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::add_random_tile()
|
void Game::add_random_tile()
|
||||||
|
@ -161,6 +164,16 @@ static bool is_stalled(const Game::Board& board)
|
||||||
return true;
|
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)
|
bool Game::slide_tiles(Direction direction)
|
||||||
{
|
{
|
||||||
size_t successful_merge_score = 0;
|
size_t successful_merge_score = 0;
|
||||||
|
@ -195,7 +208,7 @@ Game::MoveOutcome Game::attempt_move(Direction direction)
|
||||||
bool moved = slide_tiles(direction);
|
bool moved = slide_tiles(direction);
|
||||||
if (moved) {
|
if (moved) {
|
||||||
m_turns++;
|
m_turns++;
|
||||||
add_random_tile();
|
add_tile();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_complete(m_board, m_target_tile))
|
if (is_complete(m_board, m_target_tile))
|
||||||
|
@ -207,6 +220,57 @@ Game::MoveOutcome Game::attempt_move(Direction direction)
|
||||||
return MoveOutcome::InvalidMove;
|
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 Game::largest_tile() const
|
||||||
{
|
{
|
||||||
u32 tile = 0;
|
u32 tile = 0;
|
||||||
|
|
|
@ -10,8 +10,9 @@
|
||||||
|
|
||||||
class Game final {
|
class Game final {
|
||||||
public:
|
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(const Game&) = default;
|
||||||
|
Game& operator=(const Game&) = default;
|
||||||
|
|
||||||
enum class MoveOutcome {
|
enum class MoveOutcome {
|
||||||
OK,
|
OK,
|
||||||
|
@ -49,11 +50,22 @@ public:
|
||||||
private:
|
private:
|
||||||
bool slide_tiles(Direction);
|
bool slide_tiles(Direction);
|
||||||
|
|
||||||
|
void add_tile()
|
||||||
|
{
|
||||||
|
if (m_evil_ai)
|
||||||
|
add_evil_tile();
|
||||||
|
else
|
||||||
|
add_random_tile();
|
||||||
|
}
|
||||||
|
|
||||||
void add_random_tile();
|
void add_random_tile();
|
||||||
|
void add_evil_tile();
|
||||||
|
|
||||||
size_t m_grid_size { 0 };
|
size_t m_grid_size { 0 };
|
||||||
u32 m_target_tile { 0 };
|
u32 m_target_tile { 0 };
|
||||||
|
|
||||||
|
bool m_evil_ai { false };
|
||||||
|
|
||||||
Board m_board;
|
Board m_board;
|
||||||
size_t m_score { 0 };
|
size_t m_score { 0 };
|
||||||
size_t m_turns { 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_layout<GUI::VerticalBoxLayout>();
|
||||||
main_widget.set_fill_with_background_color(true);
|
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());
|
auto& board_view = main_widget.add<BoardView>(&game.board());
|
||||||
board_view.set_focus(true);
|
board_view.set_focus(true);
|
||||||
|
@ -123,7 +123,7 @@ int main(int argc, char** argv)
|
||||||
undo_stack.clear();
|
undo_stack.clear();
|
||||||
redo_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.
|
// This ensures that the sizes are correct.
|
||||||
board_view.set_board(nullptr);
|
board_view.set_board(nullptr);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue