Загрузка данных
2048.cpp:
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <algorithm>
#include "2048.h"
#include "config.h"
#if defined(HAVE_UNORDERED_MAP)
#include <unordered_map>
typedef std::unordered_map<board_t, trans_table_entry_t> trans_table_t;
#elif defined(HAVE_TR1_UNORDERED_MAP)
#include <tr1/unordered_map>
typedef std::tr1::unordered_map<board_t, trans_table_entry_t> trans_table_t;
#else
#include <map>
typedef std::map<board_t, trans_table_entry_t> trans_table_t;
#endif
/* MSVC compatibility: undefine max and min macros */
#if defined(max)
#undef max
#endif
#if defined(min)
#undef min
#endif
// Transpose rows/columns in a board:
// 0123 048c
// 4567 --> 159d
// 89ab 26ae
// cdef 37bf
static inline board_t transpose(board_t x)
{
board_t a1 = x & 0xF0F00F0FF0F00F0FULL;
board_t a2 = x & 0x0000F0F00000F0F0ULL;
board_t a3 = x & 0x0F0F00000F0F0000ULL;
board_t a = a1 | (a2 << 12) | (a3 >> 12);
board_t b1 = a & 0xFF00FF0000FF00FFULL;
board_t b2 = a & 0x00FF00FF00000000ULL;
board_t b3 = a & 0x00000000FF00FF00ULL;
return b1 | (b2 >> 24) | (b3 << 24);
}
// Count the number of empty positions (= zero nibbles) in a board.
// Precondition: the board cannot be fully empty.
static int count_empty(board_t x)
{
x |= (x >> 2) & 0x3333333333333333ULL;
x |= (x >> 1);
x = ~x & 0x1111111111111111ULL;
// At this point each nibble is:
// 0 if the original nibble was non-zero
// 1 if the original nibble was zero
// Next sum them all
x += x >> 32;
x += x >> 16;
x += x >> 8;
x += x >> 4; // this can overflow to the next nibble if there were 16 empty positions
return x & 0xf;
}
/* We can perform state lookups one row at a time by using arrays with 65536 entries. */
/* Move tables. Each row or compressed column is mapped to (oldrow^newrow) assuming row/col 0.
*
* Thus, the value is 0 if there is no move, and otherwise equals a value that can easily be
* xor'ed into the current board state to update the board. */
static row_t row_left_table [65536];
static row_t row_right_table[65536];
static board_t col_up_table[65536];
static board_t col_down_table[65536];
static float heur_score_table[65536];
static float score_table[65536];
// Heuristic scoring settings
static const float SCORE_LOST_PENALTY = 200000.0f;
static const float SCORE_MONOTONICITY_POWER = 4.0f;
static const float SCORE_MONOTONICITY_WEIGHT = 47.0f;
static const float SCORE_SUM_POWER = 3.5f;
static const float SCORE_SUM_WEIGHT = 11.0f;
static const float SCORE_MERGES_WEIGHT = 700.0f;
static const float SCORE_EMPTY_WEIGHT = 270.0f;
void init_tables() {
for (unsigned row = 0; row < 65536; ++row) {
unsigned line[4] = {
(row >> 0) & 0xf,
(row >> 4) & 0xf,
(row >> 8) & 0xf,
(row >> 12) & 0xf
};
// Score
float score = 0.0f;
for (int i = 0; i < 4; ++i) {
int rank = line[i];
if (rank >= 2) {
// the score is the total sum of the tile and all intermediate merged tiles
score += (rank - 1) * (1 << rank);
}
}
score_table[row] = score;
// Heuristic score
float sum = 0;
int empty = 0;
int merges = 0;
int prev = 0;
int counter = 0;
for (int i = 0; i < 4; ++i) {
int rank = line[i];
sum += pow(rank, SCORE_SUM_POWER);
if (rank == 0) {
empty++;
} else {
if (prev == rank) {
counter++;
} else if (counter > 0) {
merges += 1 + counter;
counter = 0;
}
prev = rank;
}
}
if (counter > 0) {
merges += 1 + counter;
}
float monotonicity_left = 0;
float monotonicity_right = 0;
for (int i = 1; i < 4; ++i) {
if (line[i-1] > line[i]) {
monotonicity_left += pow(line[i-1], SCORE_MONOTONICITY_POWER) - pow(line[i], SCORE_MONOTONICITY_POWER);
} else {
monotonicity_right += pow(line[i], SCORE_MONOTONICITY_POWER) - pow(line[i-1], SCORE_MONOTONICITY_POWER);
}
}
heur_score_table[row] = SCORE_LOST_PENALTY +
SCORE_EMPTY_WEIGHT * empty +
SCORE_MERGES_WEIGHT * merges -
SCORE_MONOTONICITY_WEIGHT * std::min(monotonicity_left, monotonicity_right) -
SCORE_SUM_WEIGHT * sum;
// execute a move to the left
for (int i = 0; i < 3; ++i) {
int j;
for (j = i + 1; j < 4; ++j) {
if (line[j] != 0) break;
}
if (j == 4) break; // no more tiles to the right
if (line[i] == 0) {
line[i] = line[j];
line[j] = 0;
i--; // retry this entry
} else if (line[i] == line[j]) {
if(line[i] != 0xf) {
/* Pretend that 32768 + 32768 = 32768 (representational limit). */
line[i]++;
}
line[j] = 0;
}
}
row_t result = (line[0] << 0) |
(line[1] << 4) |
(line[2] << 8) |
(line[3] << 12);
row_t rev_result = reverse_row(result);
unsigned rev_row = reverse_row(row);
row_left_table [ row] = row ^ result;
row_right_table[rev_row] = rev_row ^ rev_result;
col_up_table [ row] = unpack_col( row) ^ unpack_col( result);
col_down_table [rev_row] = unpack_col(rev_row) ^ unpack_col(rev_result);
}
}
static inline board_t execute_move_0(board_t board) {
board_t ret = board;
board_t t = transpose(board);
ret ^= col_up_table[(t >> 0) & ROW_MASK] << 0;
ret ^= col_up_table[(t >> 16) & ROW_MASK] << 4;
ret ^= col_up_table[(t >> 32) & ROW_MASK] << 8;
ret ^= col_up_table[(t >> 48) & ROW_MASK] << 12;
return ret;
}
static inline board_t execute_move_1(board_t board) {
board_t ret = board;
board_t t = transpose(board);
ret ^= col_down_table[(t >> 0) & ROW_MASK] << 0;
ret ^= col_down_table[(t >> 16) & ROW_MASK] << 4;
ret ^= col_down_table[(t >> 32) & ROW_MASK] << 8;
ret ^= col_down_table[(t >> 48) & ROW_MASK] << 12;
return ret;
}
static inline board_t execute_move_2(board_t board) {
board_t ret = board;
ret ^= board_t(row_left_table[(board >> 0) & ROW_MASK]) << 0;
ret ^= board_t(row_left_table[(board >> 16) & ROW_MASK]) << 16;
ret ^= board_t(row_left_table[(board >> 32) & ROW_MASK]) << 32;
ret ^= board_t(row_left_table[(board >> 48) & ROW_MASK]) << 48;
return ret;
}
static inline board_t execute_move_3(board_t board) {
board_t ret = board;
ret ^= board_t(row_right_table[(board >> 0) & ROW_MASK]) << 0;
ret ^= board_t(row_right_table[(board >> 16) & ROW_MASK]) << 16;
ret ^= board_t(row_right_table[(board >> 32) & ROW_MASK]) << 32;
ret ^= board_t(row_right_table[(board >> 48) & ROW_MASK]) << 48;
return ret;
}
/* Execute a move. */
board_t execute_move(int move, board_t board) {
switch(move) {
case 0: // up
return execute_move_0(board);
case 1: // down
return execute_move_1(board);
case 2: // left
return execute_move_2(board);
case 3: // right
return execute_move_3(board);
default:
return ~0ULL;
}
}
static inline int get_max_rank(board_t board) {
int maxrank = 0;
while (board) {
maxrank = std::max(maxrank, int(board & 0xf));
board >>= 4;
}
return maxrank;
}
static inline int count_distinct_tiles(board_t board) {
uint16_t bitset = 0;
while (board) {
bitset |= 1<<(board & 0xf);
board >>= 4;
}
// Don't count empty tiles.
bitset >>= 1;
int count = 0;
while (bitset) {
bitset &= bitset - 1;
count++;
}
return count;
}
/* Optimizing the game */
struct eval_state {
trans_table_t trans_table; // transposition table, to cache previously-seen moves
int maxdepth;
int curdepth;
int cachehits;
unsigned long moves_evaled;
int depth_limit;
eval_state() : maxdepth(0), curdepth(0), cachehits(0), moves_evaled(0), depth_limit(0) {
}
};
// score a single board heuristically
static float score_heur_board(board_t board);
// score a single board actually (adding in the score from spawned 4 tiles)
static float score_board(board_t board);
// score over all possible moves
static float score_move_node(eval_state &state, board_t board, float cprob);
// score over all possible tile choices and placements
static float score_tilechoose_node(eval_state &state, board_t board, float cprob);
static float score_helper(board_t board, const float* table) {
return table[(board >> 0) & ROW_MASK] +
table[(board >> 16) & ROW_MASK] +
table[(board >> 32) & ROW_MASK] +
table[(board >> 48) & ROW_MASK];
}
static float score_heur_board(board_t board) {
return score_helper( board , heur_score_table) +
score_helper(transpose(board), heur_score_table);
}
static float score_board(board_t board) {
return score_helper(board, score_table);
}
// Statistics and controls
// cprob: cumulative probability
// don't recurse into a node with a cprob less than this threshold
static const float CPROB_THRESH_BASE = 0.0001f;
static const int CACHE_DEPTH_LIMIT = 15;
static float score_tilechoose_node(eval_state &state, board_t board, float cprob) {
if (cprob < CPROB_THRESH_BASE || state.curdepth >= state.depth_limit) {
state.maxdepth = std::max(state.curdepth, state.maxdepth);
return score_heur_board(board);
}
if (state.curdepth < CACHE_DEPTH_LIMIT) {
const trans_table_t::iterator &i = state.trans_table.find(board);
if (i != state.trans_table.end()) {
trans_table_entry_t entry = i->second;
/*
return heuristic from transposition table only if it means that
the node will have been evaluated to a minimum depth of state.depth_limit.
This will result in slightly fewer cache hits, but should not impact the
strength of the ai negatively.
*/
if(entry.depth <= state.curdepth)
{
state.cachehits++;
return entry.heuristic;
}
}
}
int num_open = count_empty(board);
cprob /= num_open;
float res = 0.0f;
board_t tmp = board;
board_t tile_2 = 1;
while (tile_2) {
if ((tmp & 0xf) == 0) {
res += score_move_node(state, board | tile_2 , cprob * 0.9f) * 0.9f;
res += score_move_node(state, board | (tile_2 << 1), cprob * 0.1f) * 0.1f;
}
tmp >>= 4;
tile_2 <<= 4;
}
res = res / num_open;
if (state.curdepth < CACHE_DEPTH_LIMIT) {
trans_table_entry_t entry = {static_cast<uint8_t>(state.curdepth), res};
state.trans_table[board] = entry;
}
return res;
}
static float score_move_node(eval_state &state, board_t board, float cprob) {
float best = 0.0f;
state.curdepth++;
for (int move = 0; move < 4; ++move) {
board_t newboard = execute_move(move, board);
state.moves_evaled++;
if (board != newboard) {
best = std::max(best, score_tilechoose_node(state, newboard, cprob));
}
}
state.curdepth--;
return best;
}
static float _score_toplevel_move(eval_state &state, board_t board, int move) {
//int maxrank = get_max_rank(board);
board_t newboard = execute_move(move, board);
if(board == newboard)
return 0;
return score_tilechoose_node(state, newboard, 1.0f) + 1e-6;
}
float score_toplevel_move(board_t board, int move) {
float res;
struct timeval start, finish;
double elapsed;
eval_state state;
state.depth_limit = std::max(3, count_distinct_tiles(board) - 2);
gettimeofday(&start, NULL);
res = _score_toplevel_move(state, board, move);
gettimeofday(&finish, NULL);
elapsed = (finish.tv_sec - start.tv_sec);
elapsed += (finish.tv_usec - start.tv_usec) / 1000000.0;
printf("Move %d: result %f: eval'd %ld moves (%d cache hits, %d cache size) in %.2f seconds (maxdepth=%d)\n", move, res,
state.moves_evaled, state.cachehits, (int)state.trans_table.size(), elapsed, state.maxdepth);
return res;
}
/* Find the best move for a given board. */
int find_best_move(board_t board) {
int move;
float best = 0;
int bestmove = -1;
print_board(board);
printf("Current scores: heur %.0f, actual %.0f\n", score_heur_board(board), score_board(board));
for(move=0; move<4; move++) {
float res = score_toplevel_move(board, move);
if(res > best) {
best = res;
bestmove = move;
}
}
return bestmove;
}
int ask_for_move(board_t board) {
int move;
char validstr[5];
char *validpos = validstr;
print_board(board);
for(move=0; move<4; move++) {
if(execute_move(move, board) != board)
*validpos++ = "UDLR"[move];
}
*validpos = 0;
if(validpos == validstr)
return -1;
while(1) {
char movestr[64];
const char *allmoves = "UDLR";
printf("Move [%s]? ", validstr);
if(!fgets(movestr, sizeof(movestr)-1, stdin))
return -1;
if(!strchr(validstr, toupper(movestr[0]))) {
printf("Invalid move.\n");
continue;
}
return strchr(allmoves, toupper(movestr[0])) - allmoves;
}
}
/* Playing the game */
static board_t draw_tile() {
return (unif_random(10) < 9) ? 1 : 2;
}
static board_t insert_tile_rand(board_t board, board_t tile) {
int index = unif_random(count_empty(board));
board_t tmp = board;
while (true) {
while ((tmp & 0xf) != 0) {
tmp >>= 4;
tile <<= 4;
}
if (index == 0) break;
--index;
tmp >>= 4;
tile <<= 4;
}
return board | tile;
}
static board_t initial_board() {
board_t board = draw_tile() << (4 * unif_random(16));
return insert_tile_rand(board, draw_tile());
}
void play_game(get_move_func_t get_move) {
board_t board = initial_board();
int moveno = 0;
int scorepenalty = 0; // "penalty" for obtaining free 4 tiles
while(1) {
int move;
board_t newboard;
for(move = 0; move < 4; move++) {
if(execute_move(move, board) != board)
break;
}
if(move == 4)
break; // no legal moves
printf("\nMove #%d, current score=%.0f\n", ++moveno, score_board(board) - scorepenalty);
move = get_move(board);
if(move < 0)
break;
newboard = execute_move(move, board);
if(newboard == board) {
printf("Illegal move!\n");
moveno--;
continue;
}
board_t tile = draw_tile();
if (tile == 2) scorepenalty += 4;
board = insert_tile_rand(newboard, tile);
}
print_board(board);
printf("\nGame over. Your score is %.0f. The highest rank you achieved was %d.\n", score_board(board) - scorepenalty, get_max_rank(board));
}
int main() {
init_tables();
play_game(find_best_move);
}
2048.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
''' Help the user achieve a high score in a real game of 2048 by using a move searcher. '''
from __future__ import print_function
import time
import random
from ailib import ailib, to_c_board, from_c_index
# Enable multithreading?
MULTITHREAD = True
def print_board(m):
for row in m:
for c in row:
print('%8d' % c, end=' ')
print()
def to_val(m):
return [[from_c_index(c) for c in row] for row in m]
def _to_score(c):
if c <= 1:
return 0
return (c-1) * (2**c)
def to_score(m):
return [[_to_score(c) for c in row] for row in m]
if MULTITHREAD:
from multiprocessing.pool import ThreadPool
pool = ThreadPool(4)
def score_toplevel_move(args):
return ailib.score_toplevel_move(*args)
def find_best_move(m):
board = to_c_board(m)
print_board(to_val(m))
scores = pool.map(score_toplevel_move, [(board, move) for move in range(4)])
bestmove, bestscore = max(enumerate(scores), key=lambda x:x[1])
if bestscore == 0:
return -1
return bestmove
else:
def find_best_move(m):
board = to_c_board(m)
return ailib.find_best_move(board)
def movename(move):
return ['up', 'down', 'left', 'right'][move]
def play_game(gamectrl):
moveno = 0
start = time.time()
while 1:
state = gamectrl.get_status()
if state == 'ended':
break
elif state == 'won':
time.sleep(0.75)
gamectrl.continue_game()
moveno += 1
board = gamectrl.get_board()
move = find_best_move(board)
if move < 0:
break
print("%010.6f: Score %d, Move %d: %s" % (time.time() - start, gamectrl.get_score(), moveno, movename(move)))
gamectrl.execute_move(move)
board = gamectrl.get_board()
time.sleep(random.uniform(0.2, 15))
score = gamectrl.get_score()
board = gamectrl.get_board()
maxval = max(max(row) for row in to_val(board))
print("Game over. Final score %d; highest tile %d." % (score, maxval))
def parse_args(argv):
import argparse
parser = argparse.ArgumentParser(description="Use the AI to play 2048 via browser control")
parser.add_argument('-p', '--port', help="Port number to control on (default: 32000 for Firefox, 9222 for Chrome)", type=int)
parser.add_argument('-b', '--browser', help="Browser you're using. Only Firefox with remote debugging, Firefox with the Remote Control extension (deprecated), and Chrome with remote debugging, are supported right now.", default='firefox', choices=('firefox', 'firefox-rc', 'chrome', 'manual'))
parser.add_argument('-k', '--ctrlmode', help="Control mode to use. If the browser control doesn't seem to work, try changing this.", default='hybrid', choices=('keyboard', 'fast', 'hybrid', 'play2048co'))
return parser.parse_args(argv)
def main(argv):
args = parse_args(argv)
if args.browser == 'firefox':
from ffctrl import FirefoxDebuggerControl
if args.port is None:
args.port = 32000
ctrl = FirefoxDebuggerControl(args.port)
elif args.browser == 'firefox-rc':
from ffctrl import FirefoxRemoteControl
if args.port is None:
args.port = 32000
ctrl = FirefoxRemoteControl(args.port)
elif args.browser == 'chrome':
from chromectrl import ChromeDebuggerControl
if args.port is None:
args.port = 9222
ctrl = ChromeDebuggerControl(args.port)
if args.browser == 'manual':
from manualctrl import ManualControl
gamectrl = ManualControl()
elif args.ctrlmode == 'keyboard':
from gamectrl import Keyboard2048Control
gamectrl = Keyboard2048Control(ctrl)
elif args.ctrlmode == 'fast':
from gamectrl import Fast2048Control
gamectrl = Fast2048Control(ctrl)
elif args.ctrlmode == 'hybrid':
from gamectrl import Hybrid2048Control
gamectrl = Hybrid2048Control(ctrl)
elif args.ctrlmode == 'play2048co':
from gamectrl import Play2048CoControl
gamectrl = Play2048CoControl(ctrl)
if gamectrl.get_status() == 'ended':
gamectrl.restart_game()
play_game(gamectrl)
if __name__ == '__main__':
import sys
exit(main(sys.argv[1:]))