1. 程式人生 > >C語言實現2048

C語言實現2048

請仔細看完並下載課程最後提供的實現部分功能的原始檔: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函式判斷是否能移動的時候會導致函式會將當前陣列“移動”,我們只是想判斷狀態,並不想它真的移動,所以我們找個“替死鬼陣列”試出狀態即可。
// add your header comment here

#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及時告訴我。

執行截圖: