C語言 五子棋

#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <malloc.h>

struct rcd;//聲明節點結構
typedef struct rcd* Record;//節點指針別名
typedef struct rcd record;//節點別名
#define MAXIMUS 15 //定義棋盤大小

int p[MAXIMUS][MAXIMUS];//存儲對局信息
char buff[MAXIMUS*2+1][MAXIMUS*4+3];//輸出緩衝器
int Cx,Cy;//當前光標位置
int Now;//當前走子的玩家,1表明黑,2表明白
int wl,wp;//當前寫入緩衝器的列數和行數位置
char* showText;//在棋盤中央顯示的文字信息
int count;//回合數
int Putable;//指示當前是否能夠走棋
int Exiting;//1爲當場上無子並按ESC時詢問是否退出程序的狀態,2爲非此狀態
int ExiRep;//1爲當回放到最後一回合併按向後時詢問是否退出回放的狀態,2爲非此狀態
Record RecBeg,RecNow;//記錄的開始節點和當前節點

struct rcd//記錄節點結構,雙鏈表形式
{
    int X;//此記錄走棋的X座標
    int Y;//此記錄走棋的Y座標
    Record Next;//前一個記錄
    Record Back;//後一個記錄
};

Record newRecord()//記錄節點構造函數
{
    Record r=(Record)malloc(sizeof(record));//申請一個節點對象
    r->Next=NULL;//給予先後節點初值NULL
    r->Back=NULL;
    return r;
}

void Exit()//檢查退出程序
{
    int input;
    if(Exiting)//若是是第二次按下ESC
    {
        exit(0);
    }
    else//若是是第一次按下ESC則詢問是否退出程序
    {
        showText="是否退出?再次按下ESC退出,其餘鍵返回";
        Exiting=1;//指示已經按下過ESC
    }
}

void ExitRep()//檢查退出回放
{
    int input;
    if(ExiRep)//若是是第二次後移
    {
        ExiRep=3;
    }
    else//若是是第一次後移則詢問是否退出回放
    {
        showText="是否退出?再次後移退出回放,其餘鍵返回";
        ExiRep=1;//指示已經按下事後移
    }
}

void AddRecord()//添加記錄
{
    RecNow->X=Cx;//記錄座標
    RecNow->Y=Cy;
    RecNow->Next=newRecord();//建立下一個記錄節點
    RecNow->Next->Back=RecNow;//完成雙鏈表
    RecNow=RecNow->Next;//當前記錄推至下一個記錄節點
}

int DelRecord()//刪除當前記錄節點,1爲刪除成功,0爲刪除失敗
{
    Record b;//上一個節點
    if(RecNow->Back!=NULL)//越界檢查
    {
        b=RecNow->Back;//緩存上一個節點
        free(RecNow);//釋放當前節點
        RecNow=b;//當前記錄回至上一個記錄節點
        return 1;
    }
    else
    {
        return 0;//沒有節點可刪除時
    }
}

void CleanRecord()//清理全部記錄
{
    Record n;//下一個節點
    while(RecBeg->Next!=NULL)//刪除全部記錄,直到越界前爲止
    {
        n=RecBeg->Next;//記下下一個節點
        free(RecBeg);//釋放當前節點
        RecBeg=n;//當前記錄推至下一個記錄節點
    }
}

char* Copy(char* strDest,const char* strSrc)//修改過的字符串複製函數,會忽略末端的\0
{
    char* strDestCopy = strDest;
    while (*strSrc!='\0')
    {
        *strDest++=*strSrc++;
    }
    return strDestCopy;
}

void Initialize()//初始化一個對局函數
{
    int i,j;//循環變量
    system("title 對局中(按方向鍵控制光標,空格走子),Esc撤銷");
    showText="";//重置顯示信息
    count=0;//回合數歸零
    RecNow=RecBeg=newRecord();
    Exiting=0;
    for(i=0;i<MAXIMUS;i++)//重置對局數據
    {
        for(j=0;j<MAXIMUS;j++)
        {
            p[i][j]=0;
        }
    }
    Cx=Cy=MAXIMUS/2;//重置光標到中央
    Now=1;//重置當前爲黑方
}

char* getStyle(int i,int j)//得到棋盤中指定座標交點位置的字符,經過製表符拼成棋盤
{
    if(p[i][j]==1)//1爲黑子
        return "";
    else if(p[i][j]==2)//2爲白子
        return "";
    else if(i==0&&j==0)//如下爲邊緣棋盤樣式
        return "";
    else if(i==MAXIMUS-1&&j==0)
        return "";
    else if(i==MAXIMUS-1&&j==MAXIMUS-1)
        return "";
    else if(i==0&&j==MAXIMUS-1)
        return "";
    else if(i==0)
        return "";
    else if(i==MAXIMUS-1)
        return "";
    else if(j==0)
        return "";
    else if(j==MAXIMUS-1)
        return "";
    return "";//中間的空位
}

char* getCurse(int i,int j){//得到指定座標交點位置左上格的樣式,經過製表符來模擬光標的顯示
    if(Putable)//可走棋時光標爲粗線
    {
        if(i==Cx){
            if(j==Cy)
                return "";
            else if (j==Cy+1)
                return "";
        }
        else if(i==Cx+1)
        {
            if(j==Cy)
                return "";
            else if (j==Cy+1)
                return "";
        }
    }
    else//不可走棋時光標爲虛線
    {
        if(i==Cx){
            if(j==Cy)
                return "";
            else if (j==Cy+1)
                return "";
        }
        else if(i==Cx+1)
        {
            if(j==Cy)
                return "";
            else if (j==Cy+1)
                return "";
        }
    }
    return " ";//若是不在光標附近則爲空
}

void write(char* c)//向緩衝器寫入字符串
{
    Copy(buff[wl]+wp,c);
    wp+=strlen(c);
}

void ln()//緩衝器寫入位置提行
{
    wl+=1;
    wp=0;
}

void Display()//將緩衝器內容輸出到屏幕
{
    int i,l=strlen(showText);//循環變量,中間文字信息的長度
    int Offset=MAXIMUS*2+2-l/2;//算出中間文字信息居中顯示所在的橫座標位置
    if(Offset%2==1)//若是位置爲奇數,則移動到偶數,避免混亂
    {
        Offset--;
    }
    Copy(buff[MAXIMUS]+Offset,showText);//講中間文字信息複製到緩衝器
    if(l%2==1)//若是中間文字長度爲半角奇數,則補上空格,避免混亂
    {
        *(buff[MAXIMUS]+Offset+l)=0x20;
    }
    system("cls");//清理屏幕,準備寫入
    for(i=0;i<MAXIMUS*2+1;i++){//循環寫入每一行
        printf("%s",buff[i]);
        if(i<MAXIMUS*2)//寫入完每一行須要換行
            printf("\n");
    }
}

void Print()//將整個棋盤算出並儲存到緩衝器,而後調用Display函數顯示出來
{
    int i,j;//循環變量
    wl=0;
    wp=0;
    for(j=0;j<=MAXIMUS;j++)//寫入出交點左上角的字符,由於須要打印棋盤右下角,因此很以橫縱各多一次循環
    {
        for(i=0;i<=MAXIMUS;i++)
        {
            write(getCurse(i,j));//寫入左上角字符
            if(j==0||j==MAXIMUS)//若是是棋上下盤邊緣則沒有鏈接的豎線,用空格填充位置
            {
                if(i!=MAXIMUS)
                    write(" ");
            }
            else//若是在棋盤中間則用豎線承接上下
            {
                if(i==0||i==MAXIMUS-1)//左右邊緣的豎線更粗
                    write("");
                else if(i!=MAXIMUS)//中間的豎線
                    write("");
            }
        }
        if(j==MAXIMUS)//若是是最後一次循環,則只須要處理邊側字符,交點要少一排
        {
            break;
        }
        ln();//提行開始打印交點內容
        write(" ");//用空位補齊位置
        for(i=0;i<MAXIMUS;i++)//按橫座標循環正常的次數
        {
            write(getStyle(i,j));//寫入交點字符
            if(i!=MAXIMUS-1)//若是不在最右側則補充一個橫線承接左右
            {
                if(j==0||j==MAXIMUS-1)
                {
                    write("");//上下邊緣的橫線更粗
                }
                else
                {
                    write("");//中間的橫線
                }
            }
        }
        ln();//寫完一行後提行
    }
    Display();//將緩衝器內容輸出到屏幕
}

int Put(){//在當前光標位置走子,若是非空,則返回0表示失敗
    if(Putable)
    {
        p[Cx][Cy]=Now;//改變該位置數據
        AddRecord();
        return 1;//返回1表示成功
    }
    else
    {
        return 0;
    }
}

int Check()//勝負檢查,即判斷當前走子位置有沒有形成五連珠的狀況
{
    int w=1,x=1,y=1,z=1,i;//累計橫豎正斜反邪四個方向的連續相同棋子數目
    for(i=1;i<5;i++)if(Cy+i<MAXIMUS&&p[Cx][Cy+i]==Now)w++;else break;//向下檢查
    for(i=1;i<5;i++)if(Cy-i>0&&p[Cx][Cy-i]==Now)w++;else break;//向上檢查
    if(w>=5)return Now;//若果達到5個則判斷當前走子玩家爲贏家
    for(i=1;i<5;i++)if(Cx+i<MAXIMUS&&p[Cx+i][Cy]==Now)x++;else break;//向右檢查
    for(i=1;i<5;i++)if(Cx-i>0&&p[Cx-i][Cy]==Now)x++;else break;//向左檢查
    if(x>=5)return Now;//若果達到5個則判斷當前走子玩家爲贏家
    for(i=1;i<5;i++)if(Cx+i<MAXIMUS&&Cy+i<MAXIMUS&&p[Cx+i][Cy+i]==Now)y++;else break;//向右下檢查
    for(i=1;i<5;i++)if(Cx-i>0&&Cy-i>0&&p[Cx-i][Cy-i]==Now)y++;else break;//向左上檢查
    if(y>=5)return Now;//若果達到5個則判斷當前走子玩家爲贏家
    for(i=1;i<5;i++)if(Cx+i<MAXIMUS&&Cy-i>0&&p[Cx+i][Cy-i]==Now)z++;else break;//向右上檢查
    for(i=1;i<5;i++)if(Cx-i>0&&Cy+i<MAXIMUS&&p[Cx-i][Cy+i]==Now)z++;else break;//向左下檢查
    if(z>=5)return Now;//若果達到5個則判斷當前走子玩家爲贏家
    return 0;//若沒有檢查到五連珠,則返回0表示尚未玩家達成勝利
}
void ReplayMode(){
    int i,j;//循環變量
    system("title 回放中(按左鍵後退,右鍵或空格前進),Esc退出");
    showText="";//重置顯示信息
    count=0;//回合數歸零
    Putable=0;//不可走棋狀態
    RecBeg->Back=newRecord();
    RecBeg->Back->Next=RecBeg;
    RecBeg=RecBeg->Back;
    for(i=0;i<MAXIMUS;i++)//重置對局數據
    {
        for(j=0;j<MAXIMUS;j++)
        {
            p[i][j]=0;
        }
    }
    Now=1;//重置當前爲黑方
}

void RepForward()//回放模式前進
{
    if(RecNow->Next->Next!=NULL)//越界檢查
    {
        RecNow=RecNow->Next;//當前節點推至下一個記錄節點
        p[RecNow->X][RecNow->Y]=Now;//按照記錄還原一個回合
        Cx=RecNow->X;//設置光標位置
        Cy=RecNow->Y;
        Now=3-Now;//轉換當前的黑白方
    }
    else//若已達到最後則詢問退出
    {
        ExitRep();
    }
}

void RepBackward()//回放模式後退
{
    if(RecNow->Back!=NULL)//越界檢查
    {
        p[RecNow->X][RecNow->Y]=0;//按照記錄撤銷一個回合
        if(RecNow->Back->Back==NULL)//在整個棋盤沒有棋子時隱藏光標
        {
            Cx=-2;
            Cy=-2;
        }
        else if(RecNow->Back==NULL)//在只有一個棋子時移動光標到這個棋子的位置
        {
            Cx=RecNow->X;
            Cy=RecNow->Y;
        }
        else//正常狀況下移動光標到上一回合的位置
        {
            Cx=RecNow->Back->X;
            Cy=RecNow->Back->Y;
        }
        RecNow=RecNow->Back;//當前節點後退至上一個記錄節點
        Now=3-Now;//轉換當前的黑白方
        
    }
}

void ShowReplay()
{
    int input;//輸入變量
    ReplayMode();//初始化回放模式
    RecNow=RecBeg;//當前觀察從頭開始
    RepForward();//顯示第一次走棋
    while(1)//開始無限回合的死循環,直到Esc退出
    {
        if(ExiRep==3)
        {
            ExiRep=0;
            break;
        }
        Print();//打印棋盤
        input=getch();//等待鍵盤按下一個字符
        if(input==27)//若是是ESC則退出回放
        {
            return;
        }
        else if(input==0x20)//若是是空格則前進
        {
            RepForward();
            continue;
        }
        else if(input==0xE0)//若是按下的是方向鍵,會填充兩次輸入,第一次爲0xE0表示按下的是控制鍵
        {
            input=getch();//得到第二次輸入信息
            switch(input)//判斷方向鍵方向並移動光標位置
            {
            case 0x4B:
                RepBackward();//向左後退
                break;
            case 0x4D:
                RepForward();//向右前進
                continue;
            }
        }
        ExiRep=0;//未再次按後移則不許備退出
        showText="";
    }
}


void Regret()//悔棋撤銷,若是棋盤上沒有子即爲退出
{
    if(DelRecord()){//嘗試刪除當前節點,若是有節點能夠刪除則
        p[RecNow->X][RecNow->Y]=0;//撤除當前回合
        if(RecNow->Back==NULL)//若是刪除的是第一顆子則將光標移動到第一顆子原來的位置上
        {
            Cx=RecNow->X;
            Cy=RecNow->Y;
        }
        else//不然將光標移動到上一顆子上
        {
            Cx=RecNow->Back->X;
            Cy=RecNow->Back->Y;
        }
        Now=3-Now;//反轉當前黑白方
    }
    else
    {
        Exit();//若是沒有棋子能夠撤銷,則詢問退出
    }
}

int RunGame()//進行整個對局,返回贏家信息(雖然有用上)
{
    int input;//輸入變量
    int victor;//贏家信息
    Initialize();//初始化對局
    while(1){//開始無限回合的死循環,直到出現勝利跳出
        Putable=p[Cx][Cy]==0;
        Print();//打印棋盤
        input=getch();//等待鍵盤按下一個字符
        if(input==27)//若是是ESC則悔棋或退出
        {
            Regret();
            Print();
            continue;
        }
        else if(input==0x20)//若是是空格則開始走子
        {
            if(Put())//若是走子成功則判斷勝負
            {
                victor=Check();
                Now=3-Now;//輪換當前走子玩家
                count++;
                if(victor==1)//若是黑方達到勝利,顯示提示文字並等待一次按鍵,返回勝利信息
                {
                    showText="黑方勝利!按R查看回放,按其餘鍵從新開局";
                    Print();
                    input=getch();
                    if(input==0xE0)
                    {
                        getch();
                    }
                    else if(input=='R'||input=='r')
                    {
                        ShowReplay();
                    }
                    return Now;
                }
                else if(victor==2)//若是白方達到勝利,顯示提示文字並等待一次按鍵,返回勝利信息
                {
                    showText="白方勝利!按R查看回放,按其餘鍵從新開局";
                    Print();
                    input=getch();
                    if(input==0xE0)
                    {
                        getch();
                    }
                    else if(input=='R'||input=='r')
                    {
                        ShowReplay();
                    }
                    return Now;
                }else if(count==MAXIMUS*MAXIMUS)//若是回合數達到了棋盤總量,即棋盤充滿,即爲平局
                {
                    showText="平局!按R查看回放,按其餘鍵從新開局";
                    Print();
                    input=getch();
                    if(input==0xE0)
                    {
                        getch();
                    }
                    else if(input=='R'||input=='r')
                    {
                        ShowReplay();
                    }
                    CleanRecord();
                    return 0;
                }
            }
        }
        else if(input==0xE0)//若是按下的是方向鍵,會填充兩次輸入,第一次爲0xE0表示按下的是控制鍵
        {
            input=getch();//得到第二次輸入信息
            switch(input)//判斷方向鍵方向並移動光標位置
            {
            case 0x4B://
                Cx--;
                break;
            case 0x48:
                Cy--;
                break;
            case 0x4D:
                Cx++;
                break;
            case 0x50:
                Cy++;
                break;
            }
            if(Cx<0)Cx=MAXIMUS-1;//若是光標位置越界則移動到對側
            if(Cy<0)Cy=MAXIMUS-1;
            if(Cx>MAXIMUS-1)Cx=0;
            if(Cy>MAXIMUS-1)Cy=0;
        }
        Exiting=0;//未再次按下ESC則不許備退出
        showText="";
    }
}

int main()//主函數
{
    system("mode con cols=63 lines=32");//設置窗口大小
    system("color E0");//設置顏色
    while(1){//循環執行遊戲
        RunGame();
    }
}
相關文章
相關標籤/搜索