注意:

    本文为前文 ​​再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(1) —— Firefox浏览器自动运行篇 ​​ 接续篇。

=========================================

下面给出在  ​​鬼&泣​​​ / ​​2048-ai​​   中对游戏环境的设计。


游戏环境的文件:

​cpp_source/enviroment/2048_enviroment.cpp · 鬼&泣/2048-ai - Gitee.com​​​
=================================

核心函数:

void init_tables() 

static inline board_t transpose(board_t x)

static int count_empty(board_t x)

static inline board_t execute_move_0(board_t board)

static inline board_t execute_move_1(board_t board)

static inline board_t execute_move_2(board_t board)

static inline board_t execute_move_3(board_t board)

主要函数:

static board_t draw_tile()

static board_t insert_tile_rand(board_t board, board_t tile)

static board_t initial_board()


static float score_board(board_t board)



static float score_helper(board_t board, const float* table)

==================================================


再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_数组



 再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_自动生成_02


void init_tables()  函数:

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_数组_03再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_杂谈_04

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;

// 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);
}
}

View Code


游戏环境对一个游戏状态采用一个64bit长度的整数来进行表示,可以看到一个游戏状态包括16个数字,每个数字用4bit来表示,正好是16*4bit=64bit 。

由于一个格是用4bit来进行表示,那么可以表示的数字为0~15,这里分别用0~15表示0,2**1,2**2,2**3,......,2**15 。


再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_数据_05


每个格最大可以表示的数值为:


再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_数据_06


游戏状态中一行数据为4格数字,每个格数字用4bit表示(每个格可以表示的数字为0~15),一行数据用16bit表示,那么一行数据共有2**16种表示,即 65536 。

在函数  init_tables()   中遍历所有可表示状态:

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_数据_07


在2048游戏中每一步可以获得一定的得分,该得分是根据该步骤操作可以获得的新数字的大小来计算的,比如将两个2合并为一个4,那么得分就是4;如果把两个8合并为一个16,那么得分就是16。但是需要注意的是游戏自动生成的新块是不进行得分计算的。因此在2048游戏中一个游戏状态在得知整个游戏过程中自动生成的4数字块的个数就可以根据此时的游戏状态计算出此时的游戏得分。在整个游戏过程中计数共自动生成了多少4数字块,scorepenalty 变量为生成的4数字块个数乘以得分4,具体实现为在每一步新块生成时如果是 4则自动为scorepenalty 变量加 4。

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_数组_08



计算游戏状态的得分时我们分别根据不同行对应的的得分(score_table中的值)的和再减去 scorepenalty 变量即可。

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_自动生成_09


从上代码可知,每行数据用数组line表示,从左向右分别为:line[0],line[1],line[2],line[3]。


每行数据向左移动的话生成的新的行数据可以如此计算:

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_自动生成_10


 需要注意的是在这里我们默认32768为最大表示数字,也就是说两个32768合并得到依然是32768 。 

/* Pretend that 32768 + 32768 = 32768 (representational limit). */

得到的新的行数据用64bit来表示:

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_杂谈_11



由于行数据的左移动所得的新行数据等价于原行数据左右调换后的行数据的右移动所得的新行数据,给出下面计算:

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_杂谈_12


 也就是说行数据row左移动得到result,row数据的左右调换后的rev_row的右移动得到rev_result数据。

需要注意的一个问题是由于在打印游戏状态时代码:

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇_自动生成_13



也就是说计算机中对游戏状态的表示和打印给人看到的状态表示其实是上下互相调换,左右也互相调换的。