本题目来自于一位去澳洲参加暑期交流的小朋友,为期一个多月的编程课程最后的Assignment就是用C语言在命令行中实现2048.具体要求:http://webapps.cse.unsw.edu.au/webcms2/course/index.php?cid=2360
请仔细看完并下载课程最后提供的实现部分功能的源文件:2048.c
本来以为需要考虑的部分很多,不过文档中已经帮我们梳理好了思路并且实现了比较难的部分,现在我们只需要在此2048.c的基础上进行修改就可以了。
玩过2048的都知道大概的流程和界面布局,但不一定清楚其逻辑细节,这就需要我们来一步步分析。
综上,我们的思路如下:
以左移为例来讲解,现在我们需要考虑的就是以下问题:
a),向左滑动时,我们可以看到是同一行数字之间的变化,从左侧起,如果有相同元素则合并。
b), 需要注意数字0,即图中的空背景,在滑动的时候是无视的,也就是数字不会被0间隔,这时候会有特殊的“相邻列”!
在二维数组中也就是同一行的两个相邻元素(被0间隔也相当于相邻元素)是否相同,相同就叠加并计入分数。
具体分为:
如果当前元素为0,处理不为0的相邻元素,需要把它们移到左边。
如果当前元素不为0,处理不为0的相邻元素,是否需要叠加
具体阐述见代码。
综上,代码如下:2048.c
// add your header comment here
#include
#include
#include
#include
#include
// put any extra includes here, but don't delete the ones above
#define SIZE 4
// add your function_prototypes here
// The functions moveLeft, moveRight, moveUp, moveDown
// return -1 if the specified moving of numbers is not possible.
// Otherwise they move the numbers as indicated and return
// the change to the score from combining adjacent identical numbers.
// They return 0 if no numbers were combined.
/*
题目的意思是这样的,需要‘严格’判断能否移动,我用flag标示移动的时候在第二个判断的时候出错了!
拿下移为例,if(cell==0)的时候直接判断可以移动是错误的,当下移的时候,如果只有
上面第一行的某一个元素为0,下面所有行不出现合并的情况下是不能下移的。
所以在上下左右移动的过程中,当if(cell==0)即当前元素为0时要使得能够移动,就必须限制这个为0的
元素不是上面第一行(下移时)、左侧第一列(右移)、右侧第一列(左移)、下面第一行(上移)、
所以修改每个move函数中判断if(cell==0)代码的i或者j的循环条件,以避开判断时0所在的特殊行位置
具体修改看下面部分注释(修改的只有每个move函数中第二个for循环中的i或者j的循环条件)
*/
int moveLeft(int board[SIZE][SIZE]) {
int i,j,score=0,flag=-1;
for(i=0;i=0;j--)
{
int cell=board[i][j];
if(cell!=0)
{
int k=j-1;
while(k>=0)
{
int nextcell=board[i][k];
if(nextcell!=0)
{
if(cell==nextcell)
{
flag=0;
board[i][j]+=board[i][k];
score+=board[i][j];
board[i][k]=0;
}
k=-1;
break;
}
k--;
}
}
}
}
//修改部分:for循环中的i或者j的循环条件
for(i=0;i0;j--)
{
int cell=board[i][j];
if(cell==0)
{
int k=j-1;
while(k>=0)
{
int nextcell=board[i][k];
if(nextcell!=0)
{
flag=0;//当前元素为0,说明能移动,改变flag的值
board[i][j]=nextcell;
board[i][k]=0;
k=-1;
}
k--;
}
}
}
}
if(flag!=-1)
return score;
else
return -1;
}
int moveDown(int board[SIZE][SIZE]) {
int i,j,score=0,flag=-1;
for(i=SIZE-1;i>=0;i--)
{
for(j=0;j=0)
{
int nextcell=board[k][j];
if(nextcell!=0)
{
if(board[i][j]==board[k][j])
{
flag=0;
board[i][j]+=board[k][j];
score+=board[i][j];
board[k][j]=0;
}
k=0;
break;
}
k--;
}
}
}
}
//修改部分:for循环中的i或者j的循环条件
for(i=SIZE-1;i>0;i--)
{
for(j=0;j=0)
{
int nextcell=board[k][j];
if(nextcell!=0)
{
flag=0;
board[i][j]=nextcell;
board[k][j]=0;
k=0;
}
k--;
}
}
}
}
if(flag!=-1)
return score;
else
return -1;
}
int moveUp(int board[SIZE][SIZE]) {
int i,j,score=0,flag=-1;
for(i=0;i 1){
seed = atoi(argv[1]);
} else {
seed = time(0);
}
srand(seed);
printf("Enter %d numbers making up initial board:\n", SIZE * SIZE);
numbersRead = readBoard(board);
if (numbersRead != SIZE * SIZE) {
printf("Warning readBoard read only %d numbers\n", numbersRead);
}
printf("Repeat game by running: %s %u\n", argv[0], seed);
printHelp();
score = 0;
gameWon = 0;
while (gameOver(board) == 0) {
printf("\n");
printBoard(board);
printf("Your score is %d.\n", score);
if (gameWon == 0 && boardContains2048(board)) {
gameWon = 1;
printf("Congratulations you've won the game - q to quit or you can keep going\n");
}
printf("> ");
c = getchar();
while (c != EOF && isspace(c)) {
c = getchar();
}
printf("\n");
if (c == EOF || c == 'q' || c == 'Q') {
printf("Good bye - your final score was %d.\n", score);
return 0;
}
c = tolower(c);
if (!strchr("hjklaswd", c)) {
printHelp();
} else {
moveScore = 0;
if (c == 'h' || c == 'a') {
moveScore = moveLeft(board);
} else if (c == 'j' || c == 's') {
moveScore = moveDown(board);
} else if (c == 'k' || c == 'w') {
moveScore = moveUp(board);
} else if (c == 'l' || c == 'd') {
moveScore = moveRight(board);
}
if (moveScore == -1) {
printf("%c is not a legal move in the current position.\n", c);
} else {
insertNewNumber(board);
score = score + moveScore;
}
}
}
printBoard(board);
printf("Game over - your final score was %d.\n", score);
return 0;
}
// print a help message
// do not change this function
void
printHelp(void) {
printf("Enter h or a for left, j or s for down, k or w for up, l or d for right, q to quit\n");
}
// add a new number to the board
// it will either be a 2 (90% probability) or a 4 (10% probability)
// do not change this function
void insertNewNumber(int board[SIZE][SIZE]) {
int row, column;
int index, availableSquares = 0;
// count vacant squares
for (row = 0; row < SIZE; row = row + 1) {
for (column = 0; column < SIZE; column = column + 1) {
if (board[row][column] == 0) {
availableSquares = availableSquares + 1;
}
}
}
if (availableSquares == 0) {
printf("Internal error no available square\n");
exit(1);
}
// randomly pick a vacant square
index = rand() % availableSquares;
for (row = 0; row < SIZE; row = row + 1) {
for (column = 0; column < SIZE; column = column + 1) {
if (board[row][column] == 0) {
if (index == 0) {
if (rand() % 10 == 0) {
board[row][column] = 4;
} else {
board[row][column] = 2;
}
return;
}
index = index - 1;
}
}
}
}
对于随机生成数字函数不与讲解,不过我们还是大概能看懂其逻辑的。
这时一个C语言实现的丑丑的 2048Game诞生了,只要我们掌握了其中move’函数的逻辑和随机生成数字的原理,我们也可以在其他平台上利用API做出更漂亮的2048Game,但其主要逻辑就是如此了。
由于每个人代码风格不一样,所以你可能看起来吃力一点,所以你最好提前多玩几遍游戏,然后在纸上面画图分析一下每个方向上移动的逻辑。然后尝试写代码,遇到想不通的可以查看代码。这样效果好点。进行分析的时候一定要分开各个方向进行单独分析,不要揉杂在一起想。
从代码来看,看懂之后你可以改进以下几个地方:
1, 变量的命名(可以使用有意义的单词)
2, 关于gameOver函数的实现,你可以想一个更巧妙的算法。
3, 游戏逻辑基本都实现了,而且我测试了常见情况和几种极端情况,均没问题,但难免有些数据没经过测试,如果你发现了bug及时告诉我。
运行截图: