C语言实现2048

本题目来自于一位去澳洲参加暑期交流的小朋友,为期一个多月的编程课程最后的Assignment就是用C语言在命令行中实现2048.具体要求:http://webapps.cse.unsw.edu.au/webcms2/course/index.php?cid=2360

请仔细看完并下载课程最后提供的实现部分功能的源文件:2048.c

本来以为需要考虑的部分很多,不过文档中已经帮我们梳理好了思路并且实现了比较难的部分,现在我们只需要在此2048.c的基础上进行修改就可以了。

 

一步步来分析:

玩过2048的都知道大概的流程和界面布局,但不一定清楚其逻辑细节,这就需要我们来一步步分析。

  • 界面:即使是在丑丑的命令行中我们也是需要给2048一个像样的界面,当然了,我们能想到的无非也就是给它平凑出一个个完整的边框。
  • 数字:我们可以通过一个4X4数组来实现2048的布局。用数字0来模拟空格,其他数字是随机生成的,好在已经提供了可以随机产生数字的函数,不用我们来写了。
  • 操作:文档中要求分别通过字母hjkl模拟左下上右各个方向,q退出,还需要记录操作获得的分数。
  • 判断输赢:当产生2048即获胜,如果棋盘满了仍未出现即输。

 

综上,我们的思路如下:

  • 界面由一个函数打印边框即可实现。
  • 至于界面中的数字由4X4数组来实现,其中变换的无非就是数组中元素。
  • 输赢判断:数组中出现2048赢,棋盘满未达到输。

实现各个函数:

  1. main函数:仔细观察main函数逻辑对于函数的编写有很大的帮助。题目要求是不可以更改main()的,看懂其中每一步所调用的函数即可。
  2. printBoard()函数:用来打印界面
  • void printHelp(),void insertNewNumber()这两个函数已经实现用来显示提示信息和插入数字(随机插入到棋盘中);
  • int boardContains2048(int board[SIZE][SIZE]);判断是否棋盘中是否包含2048,遍历数组元素即可。
  • int readBoard(int board[SIZE][SIZE]),int printBoard(int board[SIZE][SIZE]):其实就是读入输出数组的函数,注意格式即可。
  • functionsmoveLeft,moveRightmoveDownmoveUp,它们是有相似的地方的,同时也是2048运行逻辑的主要部分。

以左移为例来讲解,现在我们需要考虑的就是以下问题:

a),向左滑动时,我们可以看到是同一行数字之间的变化,从左侧起,如果有相同元素则合并。

b), 需要注意数字0,即图中的空背景,在滑动的时候是无视的,也就是数字不会被0间隔,这时候会有特殊的“相邻列”!

在二维数组中也就是同一行的两个相邻元素(被0间隔也相当于相邻元素)是否相同,相同就叠加并计入分数。

具体分为:

如果当前元素为0,处理不为0的相邻元素,需要把它们移到左边。

如果当前元素不为0,处理不为0的相邻元素,是否需要叠加

具体阐述见代码。

 

  • int gameOver(int board[SIZE][SIZE]);为了判断是否gameover,我们只需要判断当前状态下还是能上下左右移动即可,如果都不能移动也就是输了,但我们要注意当我们把当前数组传进move函数判断是否能移动的时候会导致函数会将当前数组“移动”,我们只是想判断状态,并不想它真的移动,所以我们找个“替死鬼数组”试出状态即可。

综上,代码如下:2048.c

#include <stdio.h>  
#include <stdlib.h>  
#include <ctype.h>  
#include <string.h>  
#include <time.h>  
// 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<SIZE;i++)  
    {  
        for(j=0;j<SIZE;j++)  
        {  
            int cell=board[i][j];//cell单词用的不太恰当,表示当前元素,你可以采用更有意义的命名  
            if(cell!=0)  
            {  
                int k=j+1;  
                while(k<SIZE)  
                {  
                    int nextcell=board[i][k];  
                    if(nextcell!=0)  
                    {  
                        if(cell==nextcell)  
                        {  
                            flag=0;//相邻两个元素相同,就说明能移动,所以改变flag的值  
                            board[i][j]+=board[i][k];  
                            score+=board[i][j];  
                            board[i][k]=0;  
                        }  
                        k=SIZE;  
                        break;  
                    }  
                    k++;  
                }  
            }  
        }  
    }  
  
    //修改部分:for循环中的i或者j的循环条件  
  
    for(i=0;i<SIZE;i++)  
    {  
        for(j=0;j<SIZE-1;j++)  
        {  
            int cell=board[i][j];  
            if(cell==0)  
            {  
                int k=j+1;  
                while(k<SIZE)  
                {  
                    int nextcell=board[i][k];  
                    if(nextcell!=0)  
                    {  
                        flag=0;//  
                        board[i][j]=nextcell;  
                        board[i][k]=0;  
                        k=SIZE;  
                    }  
                    k++;  
                }  
            }  
        }  
    }  
    if(flag!=-1)  
        return score;  
    else  
        return -1;  
}  
  
int moveRight(int board[SIZE][SIZE]) {  
    int i,j,score=0,flag=-1;  
    for(i=0;i<SIZE;i++)  
    {  
        for(j=SIZE-1;j>=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;i<SIZE;i++)  
    {  
        for(j=SIZE-1;j>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)  
                    {  
                        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<SIZE;j++)  
        {  
            int cell=board[i][j];  
  
            if(cell!=0)  
            {  
                int k=i-1;  
                while(k>=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<SIZE;j++)  
        {  
            int cell=board[i][j];  
            if(cell==0)  
            {  
                int k=i-1;  
                while(k>=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<SIZE;i++)  
    {  
        for(j=0;j<SIZE;j++)  
        {  
            int cell=board[i][j];  
  
            if(cell!=0)  
            {  
                int k=i+1;  
                while(k<SIZE)  
                {  
                    int nextcell=board[k][j];  
                    if(nextcell!=0)  
                    {  
                        if(cell==nextcell)  
                        {  
                            flag=0;  
                            board[i][j]+=board[k][j];  
                            score+=board[i][j];  
                            board[k][j]=0;  
                        }  
                        k=SIZE;  
                        break;  
                    }  
                    k++;  
                }  
            }  
        }  
    }  
  
     //修改部分:for循环中的i或者j的循环条件  
    for(i=0;i<SIZE-1;i++)  
    {  
        for(j=0;j<SIZE;j++)  
        {  
            int cell=board[i][j];  
            if(cell==0)  
            {  
  
                int k=i+1;  
                while(k<SIZE)  
                {  
                    int nextcell=board[k][j];  
                    if(nextcell!=0)  
                    {  
                        flag=0;  
                        board[i][j]=nextcell;  
                        board[k][j]=0;  
                        k=SIZE;  
                    }  
                    k++;  
                }  
            }  
        }  
    }  
    if(flag!=-1)  
        return score;  
    else  
        return -1;  
}  
  
// gameOver returns 0 iff it is possible to make a move on the board  
// It returns 1 otherwise.  
  
int gameOver(int board[SIZE][SIZE]) {  
    int copy_board[SIZE][SIZE],i,j;  
    /*为了避免直接把board[][]传进move函数判断的时候改变board,所以把board复制给 
    另一个数组,然后判断,这样就不会改变board数组了 
 
    */  
    for(i=0;i<SIZE;i++)  
    {  
        for(j=0;j<SIZE;j++)  
        {  
            copy_board[i][j]=board[i][j];  
        }  
    }  
    if(moveDown(copy_board)==-1&&moveUp(copy_board)==-1&&moveLeft(copy_board)==-1&&moveRight(copy_board)==-1)//如果四个移动函数都返回-1即不能移动GameOver  
        return 1;  
    else  
        return 0;  
  
}  
  
// boardContains2048 returns 1 iff the board contains 2048.  
// It returns 0 otherwise.  
  
int boardContains2048(int board[SIZE][SIZE]) {  
    int i,j;  
    for(i=0;i<SIZE;i++)  
    {  
        for(j=0;j<SIZE;j++)  
        {  
            if(board[i][j]==2048)  
                return 1;  
        }  
    }  
    return 0;  
}  
  
// printBoard displays the board.  
  
void printBoard(int board[SIZE][SIZE]) {  
    int i,j;  
  
    printf("+");  
    for(j=0;j<SIZE;j++)  
    {  
        printf("-----");  
    }  
    printf("+\n");  
  
    for(i=0;i<SIZE;i++)  
    {  
        printf("|");  
        for(j=0;j<SIZE;j++)  
        {  
            if(board[i][j]==0)  
            {  
                char a='.';  
               printf("%5c",a);  
            }  
            else  
            {  
                 printf("%5d",board[i][j]);  
            }  
        }  
        printf("|\n\n");  
    }  
  
    printf("+");  
    for(j=0;j<SIZE;j++)  
    {  
        printf("-----");  
    }  
    printf("+\n");  
}  
  
  
// readBoard attempts to read SIZE*SIZE integers into array board  
// it returns how many integers actually were read  
  
int readBoard(int board[SIZE][SIZE]) {  
    int i,j;  
    for(i=0;i<SIZE;i++)  
    {  
        for(j=0;j<SIZE;j++)  
            scanf("%d",&board[i][j]);  
    }  
    return i*j;  
}  
  
//  
// add your functions here  
//  
  
//  
// You do not need to modify the code below here.  
//  
// If you wish to modify the code below, you have  
// misunderstood the assignment specification  
//  
  
void printHelp(void);  
void insertNewNumber(int board[SIZE][SIZE]);  
  
int main(int argc, char *argv[]) {  
    int board[SIZE][SIZE] = {{0}};  
    int c, score, moveScore, gameWon, numbersRead;  
    unsigned int seed;  
  
    // initialize random generator with command-line argument if provided  
    // or with current time  
    if (argc > 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及时告诉我。

 

运行截图:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值