C語言實現中國象棋(Qt實現界面,源碼下載,詳細註釋,易移植)

前言:中國象棋的規則很多人都懂,用C語言做一箇中國象棋遊戲,其要點是怎麼把抽象的規則變成形象、具體的代碼。本項目提供詳細的實現思路,源碼附帶大量的註釋說明,源碼逐步地實現了每一種棋類的走棋規則、喫棋規則,將每一條細化的規則整合起來也就實現了一個完整的遊戲規則。

本項目提供兩種調試方式,一種是帶界面操控(QT實現),一種是終端輸入操控。本項目非常適合初學者用以參考學習。

下載源碼:

       點擊上方鏈接下載源碼,源碼下載解壓後有兩個文件夾,分別是QT工程和Dev-C++工程,前者帶界面後者是終端輸入方式調試。

代碼設計:

     兩個工程的源碼都包含了chess.cppchess.h兩個文件,這兩個文件實現了象棋的遊戲規則,將一盤象棋遊戲的所有操作通過接口來實現,非常方便移植到其他的嵌入式平臺。下面先對這兩個源碼文件的部分內容作說明。

typedef struct{
    eChessColour Colour:4;
    eChessClass Class:4;
}ChessPiecesStr;//裝載棋子的容器,攜帶棋子顏色、棋子種類信息


/*由多個棋子容器組成的虛擬棋盤 10(row)*9(col)*/
static ChessPiecesStr CheckerBoard[10][9];

ChessPiecesStr結構體表示一個象棋包含的信息(顏色、棋類),那麼可以定義這樣一個數組ChessPiecesStr  CheckerBoard[10][9],它代表整個棋盤,10行,9列,每個數組元素代表棋盤的每一個位置上的棋子,如果某個元素的信息是NULL,則代表該位置上沒有棋子。我們在遊戲過程的走棋、喫棋、悔棋等操作,其實是對數組CheckerBoard裏的數據進行操作,然後將操作完的棋盤更新顯示出來。

typedef struct{
    ChessPiecesStr ChPs;
    uchar col,row;//位置:列、行
}PickUpChessStruct;//被選中的棋子,攜帶棋子所在的位置、棋子容器裏的信息

PickUpChessStruct結構體表示遊戲者當前要操作的棋子,它包含了該棋子的座標信息、棋色、棋類,這麼封裝是爲了方便實現每一種棋類的走棋規則、喫起規則,下面以炮的走棋規則爲例:

bool  MovemenRules_PAO(PickUpChessStruct* pActiveChess,uchar row,uchar col)//【炮】走棋規則
{
    int i,col_min,col_max,row_min,row_max;
    //【炮】走棋規則:走直線,不能跨越棋子
    //窮舉法+假設法  要麼橫着走,要麼豎着走,其他都是違規;假設成功了,那麼起點到終點之間不能有其他棋子
    if(pActiveChess->row == row){//橫着走
         if(pActiveChess->col > col){
            col_min = col;
            col_max = pActiveChess->col;
         }else{
            col_min = pActiveChess->col;
            col_max = col;
         }

        for(i = col_min+1;i < col_max; i++ ){
            if(CheckerBoard[row][i].Class != 0) return false;//假設不成立
        }
        return true;//假設成立
    }else if(pActiveChess->col == col){//豎着走
        if(pActiveChess->row > row){
            row_min = row;
            row_max = pActiveChess->row;
         }else{
            row_min = pActiveChess->row;
            row_max = row;
         }

        for(i = row_min+1;i < row_max; i++ ){
            if(CheckerBoard[i][col].Class != 0) return false;//假設不成立
        }
        return true;//假設成立
    }else{
        return false;
    }
}

函數傳入的參數包括了一個類型PickUpChessStruct的結構體代表拿起來將要操作的棋子,row、col代表落棋的位置,將這些信息傳入MovemenRules_PAO函數就能判斷是否滿足炮的走棋規則,注意這裏僅僅是判斷,CheckerBoard數組也就是棋盤,並沒有發送發生任何變化。

bool MoveChess(char Operator,uchar start_row,uchar start_col,uchar end_row,uchar end_col)//將start_row,start_col處的棋子,移到end_row,end_col位置上
{
    bool ret = false;
    PickUpChessStruct ActiveChess;

    /*判斷遊戲是否結束*/
    if(Winner != Cc_NULL) return ret;

    /*判斷選中的起始座標上是否有棋,若無棋返回*/
    if(CheckerBoard[start_row][start_col].Class == Cn_NULL){
        return ret;
    }

    /*判斷是否允許該操作者走棋*/
    if(CurOperator != Operator){
        return ret;
    }

    /*將選中的棋子信息複製到ActiveChess結構體裏*/
    ActiveChess.col = start_col;
    ActiveChess.row = start_row;
    ActiveChess.ChPs.Class = CheckerBoard[start_row][start_col].Class;
    ActiveChess.ChPs.Colour = CheckerBoard[start_row][start_col].Colour;

    //判斷落在位置是否有棋子
    if(CheckerBoard[end_row][end_col].Class != Cn_NULL){
        //落的位置有棋
        //1.判斷是否是己方棋子
        if(ActiveChess.ChPs.Colour == CheckerBoard[end_row][end_col].Colour){
            return false;
        }
        //2.判斷是否能喫
        ret = (*ChessEatRulesArry[(unsigned char)ActiveChess.ChPs.Class-1])(&ActiveChess,end_row,end_col);//該棋類的喫棋規則,將選中的棋子和要落得位置傳入
        if(ret == false){
            return ret;//不能喫,返回是失敗
        }else{
            //能喫,判斷被喫的是否是”將/帥“
            if(CheckerBoard[end_row][end_col].Class == Cn_JIANG){
                //遊戲結束
                Winner = CheckerBoard[end_row][end_col].Colour==Cc_BLACK?Cc_RED:Cc_BLACK;
            }
        }
    }else{
        //落的位置沒棋,判斷是否能走
        ret = (*ChessMovemenRulesArry[(unsigned char)ActiveChess.ChPs.Class-1])(&ActiveChess,end_row,end_col);//該棋類的走棋規則,將選中的棋子,要落得位置傳入
        if(ret == false){
            return false;
        }
    }
    //能到這裏說明符合走棋、喫棋規則,接下來進行棋子移動

    //切換操作者
    CurOperator = (CurOperator==Cc_BLACK)?Cc_RED:Cc_BLACK;

    //備份,實現悔棋功能
    pushStack(&CheckerBoard[ActiveChess.row][ActiveChess.col],ActiveChess.row,ActiveChess.col);
    pushStack(&CheckerBoard[end_row][end_col],end_row,end_col);

    //1.先棋盤中移除選中的棋
    CheckerBoard[ActiveChess.row][ActiveChess.col].Class = Cn_NULL;
    CheckerBoard[ActiveChess.row][ActiveChess.col].Colour = Cc_NULL;
    //2.將選中的棋放到新的位置 row col
    CheckerBoard[end_row][end_col].Class= ActiveChess.ChPs.Class;
    CheckerBoard[end_row][end_col].Colour= ActiveChess.ChPs.Colour;
    return ret;
}

MoveChess函數是實現走棋的功能,參數Operator代表是操作者的身份(紅棋、黑棋),start_row、start_col代表選中的棋子在棋盤的位置,end_row、end_col代表選中的棋子落棋的位置。通過起始座標可以知道選中的棋子在CheckerBoard數組裏的位置,從而知道該棋子是什麼顏色什麼棋類,再調用與該棋類相應的走棋、喫棋規則函數來判斷是否合法。處理的每一步流程都有代碼註釋,不用看代碼框架圖也很容易理解。

悔棋功能是通過棧原理實現的,棧的特點是先進後出,在棋盤上成功地走一步棋子,總共有兩個位置發生變化,不管是走棋還是喫棋,那麼只要把這兩個位置的棋子信息保存到棧裏面,並且如果棧滿了之後直接覆蓋較早的記錄,悔棋時,從棧裏取出上一步變化的兩個位置的信息,將其還原就行了。

Dev-C++工程使用

‘      Dev-C++開發工具可以從網上免費下載,上手容易,對於初學者來說是一款不錯的學習工具;打開源碼工程後,可看到包括了兩個源碼文件是chess.cpp、chess.hDev-C++工程文件Chess-alone.dev,編譯生成的可執行文件Chess-alone.exe

main函數:

int main(int argc, char *argv[]) {

    (void)argc;
    (void)argv;
    int start_row,start_col,end_row,end_col;

    InitCheckerBoard();
    CheckerBoardPrintf();

    PrintfTips();
    while(1){
        if(CurOperator == Cc_BLACK){
            printf("黑棋走棋:");
        }else{
            printf("紅棋走棋:");
        }
        fflush(stdin);
        scanf("(%d,%d)>(%d,%d)",&start_row,&start_col,&end_row,&end_col);

        if(start_row < 0 || start_row > 10 || start_col < 0 || start_col > 9||\
        end_row < 0 || end_row > 10 || end_col < 0 || end_col > 9){\
            PrintfTips();
        }

        MoveChess(CurOperator,start_row,start_col,end_row,end_col);
        CheckerBoardPrintf();
        if(Winner != Cc_NULL){
            if(Winner == Cc_BLACK){
                printf("黑棋勝利,遊戲重新開始!\n");
            }else{
                printf("紅旗勝利,遊戲重新開始!\n");
            }
            InitCheckerBoard();
            CheckerBoardPrintf();
        }
    }

    return 0;
}

Main函數主要是從終端輸入走棋的起始座標和終點座標傳入MoveChess完成走棋操作,然後再將棋盤的內容打印出來,實現了一個簡單的接口使用演示。這裏不用糾結無法區分顏色的問題。效果圖如下:

Qt工程的使用

       Qt Creator是我經常使用的一款開發工具,它可以開發跨平臺的應用,比如說同一套代碼,可以編譯成window的應用程序,還可以編譯成安卓的應用程序,Linux等,而且它提供了豐富的開發接口和說明文檔;從官網上就可以下載免費的個人開源版安裝包。

       打開工程後,裏面的文件說明如下:

 

Mainwindow.cpp文件裏重寫了窗口類裏的paintEvent函數,每當界面重繪時都會調用它,那麼就可以將象棋的背景畫出來。

棋盤的背景通過QPainter畫線實現,每一個棋子則是繼承了QPushButton類,棋盤上有9*10個這樣的按鈕,分別對應CheckerBoard數組的9*10個元素,如果某個位置上沒有棋子就把該按鈕隱藏以來。

MyPiece繼承於QPushButton後重寫了鼠標點擊、移動、釋放事件,實現當鼠標點擊棋子時,可以拖動棋子,然後落到其他位置。當鼠標釋放時,就可以得到鼠標的起始座標和終點座標,然後調用MoveChess進行走棋。

效果圖:

下面展示部分代碼:

piece.c

#include "piece.h"

MyPiece::MyPiece(QWidget *parent) :
    QPushButton(parent)
{
    //none
}

MyPiece::~MyPiece()
{
    //none
}

void MyPiece::Display(void)//將棋子顯示出來
{
    this->setText(QString(GetChessName(this->Colour,this->Class)));
    if(this->Colour == Cc_BLACK){
       this->setStyleSheet("QPushButton{background-color: rgb(255, 170, 127);"
                         "font-size:24px;color:black;border-style:solid;"
                         "border-width:1px;border-color:black;border-radius:20px;}");
   }else{
       this->setStyleSheet("QPushButton{background-color: rgb(255, 170, 127);"
                         "font-size:24px;color:red;border-style:solid;"
                         "border-width:1px;border-color:black;border-radius:20px;}");
   }
   this->setHidden(false);
}

void MyPiece::mousePressEvent(QMouseEvent *e)//棋子點擊事件
{
    //qDebug() <<e;
    Point_Start = this->pos();
    Point_Click = e->globalPos()-this->pos();
    /*將此小部件提升到父小部件堆棧的頂部*/
    this->raise();
}

void MyPiece::mouseMoveEvent(QMouseEvent *e)//棋子移動事件
{
    //qDebug() <<e;
    tPoint = e->globalPos()-Point_Click;
    /*限制棋子不能被拖到棋盤外*/
    tPoint.rx() = tPoint.rx() < 40?40:tPoint.rx();
    tPoint.rx() = tPoint.rx() > 520?520:tPoint.rx();
    tPoint.ry() = tPoint.ry() < 40?40:tPoint.ry();
    tPoint.ry() = tPoint.ry() > 580?580:tPoint.ry();
    /*讓棋子跟着鼠標走*/
    this->move(tPoint);
}

void MyPiece::mouseReleaseEvent(QMouseEvent *e)//棋子釋放事件
{
    (void)e;
    unsigned char x_start,y_start,x_end,y_end;

   /*將棋子的起點和終點的像素位置轉換成在棋盤裏的座標*/
    x_start=Point_Start.rx()/60;
    y_start=Point_Start.ry()/60;
    x_end=tPoint.rx()/60;
    y_end=tPoint.ry()/60;
    qDebug("(%d,%d)->(%d,%d)",x_start,y_start,x_end,y_end);
    /*得到起點和終點座標後,還原鼠標選中的棋子,發出走棋信號就行,判斷是否真的移動到終點交給外部處理*/
    this->move(Point_Start);
    /*若位置發生了改變,則發出走棋信號*/
    if(x_start != x_end || y_start != y_end){
        emit GoChess(y_start,x_start,y_end,x_end);
    }
}

可擴展的地方

  1. 目前只是實現了在一臺電腦上對戰的遊戲模式,可以對模式進行擴展,利用tcp/udp通訊協議,實現局域網對戰模式。
  2. 可以給棋盤提供豐富的背景,調整棋子上的字體,棋子的外形、顏色。
  3. 目前走棋只能通過鼠標拖動,可以增加一種點擊的方式,鼠標左鍵點擊一個棋子,再點擊另一個位置來完成走棋操作。
  4. 可以將chess.c chess.h裏的內容用面向對象的方法實現,因爲這裏考慮到方便移植到其他的嵌入式平臺,所以用C語言編寫。
  5. 可以優化以下走棋規則、喫棋規則的代碼,這裏用的C語言充分體現了其面向過程編程的特點,其中游戲處理流程可以簡化。

 

如有錯誤之處,還望指出,謝謝閱讀,求贊。

 

 

《兼聽則明,偏信則闇。———資治通鑑》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章