使用python完成2048的代码编写。
2048游戏规则:简单的移动方向键让数字叠加,并且获得这些数字每次叠加后的得分,当出现2048这个数字时游戏胜利。同时每次移动方向键时,都会在这个4*4的方格矩阵的空白区域随机产生一个数字2或者4,如果方格被数字填满了,那么就GameOver了。
实现思路:2048游戏的全部操作都是围绕着一个4*4的矩阵进行,每次从用户界面获取用户的操作(即移动方向),然后重新计算这个4*4矩阵的状态,最后刷新用户界面显示4*4矩阵的最新状态,不断的循环这个过程,直到出现2048或没有空白方块了,如下是一个处理流程示意图:
Python控制台程序,用字符(W/S/A/D)代表方向键的输入,以数字0代表空白方格。计算部分:以向左移动为例,4*4矩阵在接收到向左移动的指令后,应该将每行的数字向左叠加, 将一行的叠加操作定义为函数 handle(list, direction),其第一个参数用来存储4*4矩阵中的某一行(列),第二个参数表示移动的方向(上下左右)。 这样当左右移动方向键时,可以这样来计算矩阵:遍历矩阵的每行,并将每行的数字沿左或右进行叠加操作,for row in matrix:handle(row, direction)。对于上下移动方向键时,由于矩阵是按行存储的,不能直接处理矩阵中的列,可以通过变通采用上面的函数handle()。对于矩阵中每一列,先将其拷贝到一个列表中,然后调用handle()函数对该列表进行叠加处理,最后再将叠加后的新列表拷贝回原始矩阵中其所在的列,其逻辑上等同于下面的代码操作。
# -*- coding:UTF-8 -*-
#! /usr/bin/python
import random
import sys
v = [[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0] ]
def display(v, score):
'''显示界面
'''
print '{0:4} {1:4} {2:4} {3:4}'.format(v[0][0], v[0][1], v[0][2], v[0][3])
print '{0:4} {1:4} {2:4} {3:4}'.format(v[1][0], v[1][1], v[1][2], v[1][3])
print '{0:4} {1:4} {2:4} {3:4}'.format(v[2][0], v[2][1], v[2][2], v[2][3])
print '{0:4} {1:4} {2:4} {3:4}'.format(v[3][0], v[3][1], v[3][2], v[3][3]), ' Total score: ', score
def init(v):
'''随机分布网格值
'''
for i in range(4):
v[i] = [random.choice([0, 0, 0, 2, 2, 4]) for x in v[i]]
def align(vList, direction):
'''对齐非零的数字
direction == 'left':向左对齐,例如[8,0,0,2]左对齐后[8,2,0,0]
direction == 'right':向右对齐,例如[8,0,0,2]右对齐后[0,0,8,2]
'''
# 移除列表中的0
for i in range(vList.count(0)):
vList.remove(0)
# 被移除的0
zeros = [0 for x in range(4 - len(vList))]
# 在非0数字的一侧补充0
if direction == 'left':
vList.extend(zeros)
else:
vList[:0] = zeros
def addSame(vList, direction):
'''在列表查找相同且相邻的数字相加, 找到符合条件的返回True,否则返回False,同时还返回增加的分数
direction == 'left':从右向左查找,找到相同且相邻的两个数字,左侧数字翻倍,右侧数字置0
direction == 'right':从左向右查找,找到相同且相邻的两个数字,右侧数字翻倍,左侧数字置0
'''
score = 0
if direction == 'left':
for i in [0, 1, 2]:
if vList[i] == vList[i+1] != 0:
vList[i] *= 2
vList[i+1] = 0
score += vList[i]
return {'bool':True, 'score':score}
else:
for i in [3, 2, 1]:
if vList[i] == vList[i-1] != 0:
vList[i-1] *= 2
vList[i] = 0
score += vList[i-1]
return {'bool':True, 'score':score}
return {'bool':False, 'score':score}
def handle(vList, direction):
'''处理一行(列)中的数据,得到最终的该行(列)的数字状态值, 返回得分
vList: 列表结构,存储了一行(列)中的数据
direction: 移动方向,向上和向左都使用方向'left',向右和向下都使用'right'
'''
totalScore = 0
align(vList, direction)
result = addSame(vList, direction)
while result['bool'] == True:
totalScore += result['score']
align(vList, direction)
result = addSame(vList, direction)
return totalScore
def operation(v):
'''根据移动方向重新计算矩阵状态值,并记录得分
'''
totalScore = 0
gameOver = False
direction = 'left'
op = raw_input('operator:')
if op in ['a', 'A']: # 向左移动
direction = 'left'
for row in range(4):
totalScore += handle(v[row], direction)
elif op in ['d', 'D']: # 向右移动
direction = 'right'
for row in range(4):
totalScore += handle(v[row], direction)
elif op in ['w', 'W']: # 向上移动
direction = 'left'
for col in range(4):
# 将矩阵中一列复制到一个列表中然后处理
vList = [v[row][col] for row in range(4)]
totalScore += handle(vList, direction)
# 从处理后的列表中的数字覆盖原来矩阵中的值
for row in range(4):
v[row][col] = vList[row]
elif op in ['s', 'S']: # 向下移动
direction = 'right'
for col in range(4):
# 同上
vList = [v[row][col] for row in range(4)]
totalScore += handle(vList, direction)
for row in range(4):
v[row][col] = vList[row]
else:
print('Invalid input, please enter a charactor in [W, S, A, D] or the lower')
return {'gameOver':gameOver, 'score':totalScore}
# 统计空白区域数目 N
N = 0
for q in v:
N += q.count(0)
# 不存在剩余的空白区域时,游戏结束
if N == 0:
gameOver = True
return {'gameOver':gameOver, 'score':totalScore}
# 按2和4出现的几率为3/1来产生随机数2和4
num = random.choice([2, 2, 2, 4])
# 产生随机数k,上一步产生的2或4将被填到第k个空白区域
k = random.randrange(1, N+1)
n = 0
for i in range(4):
for j in range(4):
if v[i][j] == 0:
n += 1
if n == k:
v[i][j] = num
break
return {'gameOver':gameOver, 'score':totalScore}
init(v)
score = 0
print 'Input:W(Up) S(Down) A(Left) D(Right), press .'
while True:
display(v, score)
result = operation(v)
if result['gameOver'] == True:
print 'Game Over, You failed!'
print 'Your total score:', score
sys.exit(1)
else:
score += result['score']
if score >= 2048:
print 'Game Over, You Win!!!'
print 'Your total score:', score
sys.exit(0)
(2)修改出现例如(4,4,8,0)这样的情况,得分不合理的情况
# -*- coding:UTF-8 -*-
#!/usr/bin/python2
import random
import os, sys
v = [[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
def display(v, score):
print "%4d %4d %4d %4d" % (v[0][0], v[0][1], v[0][2], v[0][3])
print "%4d %4d %4d %4d" % (v[1][0], v[1][1], v[1][2], v[1][3])
print "%4d %4d %4d %4d" % (v[2][0], v[2][1], v[2][2], v[2][3])
print "%4d %4d %4d %4d" % (v[3][0], v[3][1], v[3][2], v[3][3])
print "Total score: %d" % score
def init(v):
for i in range(4):
v[i] = [random.choice([0, 0, 0, 2, 2, 4]) for x in range(4)]
def align(vList, direction):
for i in range(vList.count(0)):
vList.remove(0)
zeros = [0 for x in range(4 - len(vList))]
if direction == 'left':
vList.extend(zeros)
else:
vList[:0] = zeros
def addSame(vList, direction):
score = 0
if direction == 'left':
for i in [0, 1, 2]:
align(vList, direction)
if vList[i] == vList[i+1] != 0:
vList[i] *= 2
vList[i+1] = 0
score += vList[i]
return {'bool':True, 'score':score}
else:
for i in [3, 2, 1]:
align(vList, direction)
if vList[i] == vList[i-1] != 0:
vList[i] *= 2
vList[i-1] = 0
score += vList[i]
return {'bool':True, 'score':score}
return {'bool':False, 'score':score}
def handle(vList, direction):
totalScore = 0
align(vList, direction)
result = addSame(vList, direction)
while result['bool'] == True:
totalScore += result['score']
align(vList, direction)
result = addSame(vList, direction)
return totalScore
def operation(v):
totalScore = 0
gameOver = False
direction = 'left'
op = raw_input('operator:')
if op in ['a','A']:
direction = 'left'
for row in range(4):
totalScore += handle(v[row], direction)
elif op in ['d','D']:
direction = 'right'
for row in range(4):
totalScore += handle(v[row], direction)
elif op in ['w', 'W']:
direction = 'left'
for col in range(4):
vList = [v[row][col] for row in range(4)]
totalScore += handle(vList, direction)
for row in range(4):
v[row][col] = vList[row]
elif op in ['s', 'S']:
direction = 'right'
for col in range(4):
vList = [v[row][col] for row in range(4)]
totalScore += handle(vList, direction)
for row in range(4):
v[row][col] = vList[row]
else:
print "Invalid input,please enter a charactor in [W,S,A,D] or the lower"
gameOver = True
return {'gameOver':gameOver,'score':totalScore}
N = 0
for q in v:
N += q.count(0)
if N == 0:
gameOver = True
return {'gameover':gameOver,'score':totalScore}
num = random.choice([2,2,2,4])
k = random.randrange(1, N+1)
n = 0
for i in range(4):
for j in range(4):
if v[i][j] == 0:
n += 1
if n == k:
v[i][j] = num
break
return {'gameOver':gameOver, 'score':totalScore}
init(v)
score = 0
print "Input:W(Up) S(Down) A(Left) D(Right), press ."
while True:
os.system("clear")
display(v, score)
result = operation(v)
print result
if result['gameOver'] == True:
print "Game Over, You failed!"
print "Your total score %d" % (score)
sys.exit(1)
else:
score += result['score']
if score >= 2048:
print "Game Over, You Win!!!"
print "Your total score: %d" % (score)
sys.exit(0)
(3)linux使用ncurses库实现的
2048 ai
/***********************************
*makefile:
*CC=gcc
*CFLAGS=-g -O3 -lncurses
*
*.PHONY : all
*all: 2048.c
* $(CC) 2048.c -o 2048 $(CFLAGS)
************************************
*---Play----
* make
* ./2048 <-- Normal mode
* ./2048 -s <-- Play with AI suggestion
* ./2048 -a <-- Watch AI plays it. Good luck!
************************************/
#include
#include
#include
#include
#include
#include
#define AUTO
#define SIZE (4)
typedef int board_t[SIZE][SIZE];
struct hash_elem {
int hash;
int depth;
double value;
};
enum input {
LEFT = 0,
RIGHT = 1,
UP = 2,
DOWN = 3,
QUIT = 4
};
/* Static mappings & initialzation ***********************************/
/* Weight of each value */
int value_weight[16];
/* Mapping from value to power of 2 form */
int value_real[16];
/* Default search depth for each #zero-blocks */
int depth_map[] = {6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4};
/* Weight map of cells */
static const board_t cell_weight = {
{17, 13, 11, 10},
{13, 10, 9, 9},
{11, 9, 8, 8},
{10, 9, 8, 8}
};
/* Used for hash table */
static const board_t primes = {
{22189, 28813, 37633, 43201},
{47629, 60493, 63949, 65713},
{69313, 73009, 76801, 84673},
{106033, 108301, 112909, 115249}
};
void init() {
int i;
int cur_weight = 1;
int cur_real = 2;
for (i = 1; i < 16; i++) {
value_weight[i] = cur_weight;
value_real[i] = cur_real;
cur_weight *= 3;
cur_real *= 2;
}
}
/* Util Functions *****************************************************/
long gettime() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 * 1000 + tv.tv_usec;
}
void draw_grid(int y, int x) {
mvprintw(y++, x, "#####################################");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "#-----------------------------------#");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "#-----------------------------------#");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "#-----------------------------------#");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "# | | | #");
mvprintw(y++, x, "#####################################");
mvprintw(y+2, x, "Control: wasd Exit: q");
}
void board_dump(board_t b, int y, int x) {
int i, j;
draw_grid(y, x);
for (i = 0; i < SIZE; i++) {
for (j = 0; j < SIZE; j++) {
if (b[i][j])
mvprintw(i * 4 + 2 + y, j * 9 + 3 + x, "%d", value_real[b[i][j]]);
}
}
}
int board_count_zero(board_t b) {
int cnt = 0;
int i, j;
for (i = 0; i < SIZE; i++) {
for (j = 0; j < SIZE; j++) {
if (b[i][j] == 0)
cnt++;
}
}
return cnt;
}
void board_clear(board_t b) {
int i, j;
for (i = 0; i < SIZE; i++) {
for (j = 0; j < SIZE; j++) {
b[i][j] = 0;
}
}
}
int board_hash(board_t b) {
int i, j;
int hash = 0;
for (i = 0; i < SIZE; i++) {
for (j = 0; j < SIZE; j++) {
hash += b[i][j] * primes[i][j];
}
}
return hash;
}
void board_rnd_gen_cell(board_t b) {
int i, j;
int cnt = board_count_zero(b);
int gen = random() % cnt;
int n = (random() % 10) == 0 ? 2 : 1;
cnt = 0;
for (i = 0; i < SIZE; i++) {
for (j = 0; j < SIZE; j++) {
if (b[i][j] == 0) {
if (cnt == gen) {
b[i][j] = n;
return;
}
cnt++;
}
}
}
}
void delay() {
struct timespec t;
t.tv_sec = 0;
t.tv_nsec = 10000000;
nanosleep(&t, NULL);
}
/* Performance statistic */
long stat_time[16];
long stat_count[16];
void stat(int depth, long time) {
stat_count[depth]++;
stat_time[depth] += time;
}
void stat_dump() {
int i;
int line = 0;
mvprintw(25 + line++, 8, "Performance Stat");
for (i = 0; i < 16; i++) {
if (!stat_count[i])
continue;
mvprintw(25 + line++, 8, "[Depth %d] %ld us * %d times",
i, stat_time[i] / stat_count[i], stat_count[i]);
}
}
/* Game logic: Move to a direction **************************************************/
/* Return score earned, return 1 if moved with zero score */
#define movefunc(src_cell, combine_cell, nocombine_cell)\
{\
int i, j = 0;\
int moved = 0;\
int score = 0;\
for (i = 0; i < SIZE; i++) {\
int last = 0;\
int j2 = 0;\
for (j = 0; j < SIZE; j++) {\
int v = src_cell;\
if (v == 0) {\
continue;\
}\
if (v == last) {\
last = 0;\
combine_cell = v + 1;\
score += value_real[v + 1];\
} else {\
if (j2 < j)\
moved = 1;\
last = v;\
nocombine_cell = v;\
j2++;\
}\
}\
}\
return score ? score : moved;\
}
#define REVERSE(i) (SIZE - 1 - (i))
int move_left(board_t src, board_t dst) {
movefunc(src[i][j], dst[i][j2 - 1], dst[i][j2]);
}
int move_right(board_t src, board_t dst) {
movefunc(src[i][REVERSE(j)], dst[i][REVERSE(j2 - 1)], dst[i][REVERSE(j2)]);
}
int move_up(board_t src, board_t dst) {
movefunc(src[j][i], dst[j2 - 1][i], dst[j2][i]);
}
int move_down(board_t src, board_t dst) {
movefunc(src[REVERSE(j)][i], dst[REVERSE(j2 - 1)][i], dst[REVERSE(j2)][i]);
}
/* AI-related functions **************************************************/
double value(board_t b, int depth, int *choice, double max);
/* Immediate value score estimation for a board */
int imm_value(board_t b) {
int i, j;
int result = 0;
for (i = 0; i < SIZE; i++) {
for (j = 0; j < SIZE; j++) {
result += value_weight[b[i][j]] * cell_weight[i][j];
}
}
return result;
}
#define HASH_SIZE (35317)
struct hash_elem vcache[HASH_SIZE];
void cache_board_value(board_t b, int depth, double value) {
int hash = board_hash(b);
int index = hash % HASH_SIZE;
vcache[index].hash = hash;
vcache[index].value = value;
vcache[index].depth = depth;
}
int qcnt;
int qmiss;
double query_board_value(board_t b, int depth) {
int hash = board_hash(b);
int index = hash % HASH_SIZE;
qcnt++;
if (vcache[index].hash == hash && vcache[index].depth >= depth) {
return vcache[index].value;
}
qmiss++;
return -1;
}
/* Generate 2/4 at every posible position, return the average value score
* b : the board
* depth : depth of the recursive search
* max : current maximum value score
* sampled : sample rate, 0 means no sample
*/
double rnd_value(board_t b, int depth, double max, int sampled) {
int i, j;
int cnt = 0;
double sum = 0;
static int scnt = 0;
for (i = 0; i < SIZE; i++) {
for (j = 0; j < SIZE; j++) {
/* Check zero */
if (b[i][j])
continue;
/* Do sampling to reduce computation if needed */
if (sampled) {
scnt++;
if(scnt % sampled)
continue;
}
cnt += 9;
b[i][j] = 1;
sum += 9 * value(b, depth, NULL, max);
/* We do not take random 4 into consideration as it is rare */
if (depth >= 5) {
cnt += 1;
b[i][j] = 2;
sum += value(b, depth, NULL, max);
}
/**/
b[i][j] = 0;
}
}
return sum / cnt;
}
/* Return the value score for given board, zero score means died
* b : the board
* depth : depth of the recursive search
* choice : used to return the final choise of movement
* max : current maximum value score
*/
double value(board_t b, int depth, int *choice, double max) {
/* Estimate the value score */
int estimate = imm_value(b);
/* Decrease depth if estimation is too low */
if (estimate < max * 0.7)
depth--;
/* Return estimation at level 0 */
if (depth <= 0)
return estimate;
/* Adjust next depth according to depth_map */
int next_depth = depth - 1;
if (depth > 3) {
int zeros = board_count_zero(b);
if (next_depth > depth_map[zeros])
next_depth--;
}
int i;
int moved[4];
double maxv = 0;
board_t tmp[4] = {0};
int my_choice = QUIT; /* Default choice */
if (!choice) {
double v = query_board_value(b, depth);
if (v >= 0)
return v;
}
moved[LEFT] = move_left(b, tmp[LEFT]);
moved[RIGHT] = move_right(b, tmp[RIGHT]);
moved[UP] = move_up(b, tmp[UP]);
moved[DOWN] = move_down(b, tmp[DOWN]);
/* Estimate the maximum value score */
if (depth > 2)
for (i = 0; i < 4; i++) {
int v = imm_value(tmp[0]);
max = v > max ? v : max;
}
/* Try all the four direction */
for (i = 0; i < 4; i++) {
int c;
if (!moved[i])
continue;
int sample = 0; //depth < 3 ? 3 : 1;
double v = rnd_value(tmp[i], next_depth, max, sample);
if (v > maxv) {
my_choice = i;
maxv = v;
max = maxv;
}
}
if (choice)
*choice = my_choice;
cache_board_value(b, depth, maxv);
return maxv;
}
/* Game logic: Control and Interface *************************************/
static int get_AI_input(board_t b) {
int choice;
int zeros = board_count_zero(b);
long start = gettime();
double v = value(b, depth_map[zeros], &choice, 0);
long timeval = gettime() - start;
stat(depth_map[zeros], timeval);
return choice;
}
static int get_keyboard_input() {
char c;
while(1) {
//c = getchar();
c = getch();
switch(c) {
case 'w': return UP;
case 'a': return LEFT;
case 's': return DOWN;
case 'd': return RIGHT;
case 'q': return QUIT;
}
}
}
int auto_play = 0;
int suggestion = 0;
void game_loop() {
board_t a = {0};
board_t b = {0};
board_t *cur;
board_t *next;
int input;
int AI_input;
int score = 0;
cur = &a;
next = &b;
board_rnd_gen_cell(*cur);
while (1) {
clear();
/* Draw the board */
board_dump(*cur, 4, 8);
// stat_dump();
/* AI computation */
if (auto_play || suggestion) {
AI_input = get_AI_input(*cur);
const char *move_text[] = {"Left", "Right", "Up", "Down", "Game Over"};
mvprintw(1, 8, "Suggest: %s", move_text[AI_input]);
}
mvprintw(2, 8, "Score: %d", score);
/* Update screen */
refresh();
/* Get input */
if (auto_play) {
input = AI_input;
} else {
input = get_keyboard_input();
}
int moved = 0;
switch(input) {
case UP:
moved = move_up(*cur, *next); break;
case LEFT:
moved = move_left(*cur, *next); break;
case DOWN:
moved = move_down(*cur, *next); break;
case RIGHT:
moved = move_right(*cur, *next); break;
case QUIT:
return;
default:
continue;
}
if (!moved)
continue;
if (moved != 1)
score += moved;
/* Generate new cell */
board_rnd_gen_cell(*next);
/* Switch cur and next */
board_t *temp = cur;
cur = next;
next = temp;
board_clear(*next);
}
}
int main(int argc, char *argv[]) {
int opt;
while ((opt = getopt(argc, argv, "as")) != -1) {
switch (opt) {
case 'a':
auto_play = 1;
break;
case 's':
suggestion = 1;
break;
default: /* '?' */
fprintf(stderr, "Usage: %s [-a] [-s]\r\n", argv[0]);
fprintf(stderr, "-a: Let AI play the game\r\n");
fprintf(stderr, "-s: Display AI suggestion\r\n");
exit(EXIT_FAILURE);
}
}
init();
srandom(time(NULL));
initscr();
noecho();
game_loop();
refresh();
endwin();
return 0;
}
编译:gcc -g -O3 -o 2048 2048.c -lncurses
(1)代码皆是总结而至,具体细节还待以后深入研究。
(2)代码中有比较多的问题,有待完善的地方还很多。