1. 程式人生 > >C語言之三字棋的簡單實現及擴充套件

C語言之三字棋的簡單實現及擴充套件

C語言之三字棋的實現及擴充套件

 

在我們學習完陣列之後,我們完全可以利用陣列相關知識來寫一個微小型的遊戲,比如說今天所說的——三子棋。

 

大綱:

   檔案組成

   實現

   完整程式碼展示

   擴充套件

即:

 

 

 

 

 

 

一.檔案組成:

在我們學習的過程中,我們要逐漸習慣多檔案的書寫方式,也就是模組化書寫。

在本文中,筆者分為了三個檔案來寫,分別是:

  1.game.h——實現遊戲函式的宣告

  2.game.c——遊戲函式的實現

  3.test.c —— 測試及遊戲函式的呼叫

 

二.實現

 

0.檔案的初始化

在這裡我們分別在我們所建立的 test.c 和 game.c 包含我們的標頭檔案——game.h

 

 

 

1.選單的實現

在選單中,我們設定玩家可以選擇的模式,play and quit

以及,選單怎麼樣多次迴圈選擇,選單的容錯處理。這裡,我們利用 do-while 來實現。

#define _CRT_SECURE_NO_WARNINGS 1//加這一句話是因為筆者採用的是 VS 編譯器,為了防止一些不必要的錯誤出現

#include "game.h"

void menu()//列出可供玩家選擇的模式
{
    printf("**************************************************************\n");
    printf("*****************         1.play              ****************\n");
    printf("*****************         0.exit              ****************\n");
    printf("**************************************************************\n");
}

void play()
{

}


int main()
{
    int input;//在這裡,我們利用玩家選擇的模式來控制迴圈的終止
    do
    {
        menu();
        printf("請輸入你的選擇:");
        scanf("%d", &input);
        switch (input)
        {
        case 1://play
            play();
            break;
        case 0://退出
            printf("歡迎下次再來!\n");
            break;
        default://當玩家輸入了非法字元,讓其重新選擇
            printf("輸入錯誤,請重新輸入!\n");
        }
    } while (input);//當input為0時,停止迴圈
    
    return 0;
}

執行效果:

 

 

 現在,我們的選單已經做好了,接下來要做的就是來列印我們的棋盤。

 

2.棋盤的列印

這裡我們把列印函式的宣告放在 game.h 檔案裡,把實現放在game.c 檔案中。

在寫程式碼之前,我們先來想一想在棋盤列印中,我們能不能直接列印空格——這肯定是不能的,因為這樣,我們在螢幕上什麼都看不見 (≧∇≦)ノ

game.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

void InitBoard(char board[3][3], int row, int col)//棋盤初始化
{
    int i = 0, j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            board[i][j] = ' ';
        }
    }
}

void DisplayBoard(char board[3][3], int row, int col)//棋盤列印函式
{
    int i = 0, j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            printf(" %c ", board[i][j]);
            if (j < col - 1)
            {
                printf("|");//分割列
            }
        }
        printf("\n");

        if (i < row - 1)
        {
            for (j = 0; j < 3; j++)
            {
                printf("---");//分割行
                if (j < col - 1)
                {
                    printf("|");
                }
            }
        }    
        printf("\n");
    }
}

注:

  為了避免文章贅餘,test.c 以及 game.h不再表示

執行結果:

 

 

 

但是,我們這麼寫,會不會有問題?

 

 

值得注意的是,在這有人會把棋盤列印寫成這個樣子

void DisplayBoard(char board[3][3], int row, int col)//棋盤列印函式
{
    for (int i = 0; i < 3; i++)
    {
        printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
        if (i < 2)
        {
            printf("---|---|---\n");//分割行
        }    
    }
}

這樣,無非還是上面那個問題,程式碼寫死,無法擴充套件

 

所以,我們在這利用巨集來實現,棋盤的大小隨我們的巨集來改變

因此在這我們給出標頭檔案的部分

#pragma once

#include<stdio.h>

#define ROW 3//利用巨集來實現棋盤的大小
#define COL 3

void InitBoard(char board[ROW][COL], int row, int col);//棋盤初始化

void DisplayBoard(char board[ROW][COL],int row, int col);//棋盤列印函式

 

3.棋盤下子

1.玩家下子

在這裡我們一共要注意幾點:

  1.在下子之前,我們要判斷玩家所要下的位置是否在棋盤內

  2.檢測玩家要下的位置是否已有了棋子

  3.下子之後,檢查棋盤的輸贏狀況 (這個我們後面再說)

 

void PlayerMove(char board[ROW][COL], int row, int col)//玩家下棋
{
    int x, y;
    printf("玩家走:\n");
    printf("請輸入你所要落子的座標:");
    scanf("%d%d", &x, &y);
    if (board[x - 1][y - 1]!=' ')//座標被佔用
    {
        printf("該座標已被佔用,請重新下子!\n");
    }
    else if (!((x > 0 && x <= row) && (y > 0 && y <= col)))
    {
        printf("該座標為非法座標,請重新輸入!\n");//座標非法
    }
    else
    {
        board[x - 1][y - 1] = '*';//玩家落子,暫時用 * 來表示
    }
}

2.電腦下子

再這裡我們暫不深究,使用隨機函式來生成一個座標來下子

void ComputerMove(char board[ROW][COL], int row, int col)//電腦下棋
{
    int x, y;
    printf("電腦走:");
    while (1)
    {
         x = rand() % row;
         y = rand() % col;
         if (board[x][y] == ' ')
         {
             board[x][y] = '#';//這裡我們用 # 來表示電腦下棋
             break;
         }
    }
}

4.勝負的判定

在這裡,我們用一個函式的返回值來表示輸贏的各個情況。

#-----電腦贏

*-----玩家贏

C-----繼續下子

F-----和局

int ISFULL(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            if (board[i][j] == ' ')
                return 0;
        }
    }
    return 1;
}

char ISWIN(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;
for (i = 0; i < row; i++)
    {
        for (j = 0; j < col - 2; j++)
        {
            //判斷橫行
            if (board[i][j] == board[i][j + 1] && board[i][j] == board[i][j + 2] && board[i][j] != ' ')
                return board[i][j];
            //判斷主對角線
            else if (board[i][j] == board[i + 1][j + 1] && board[i + 2][j + 2] == board[i][j] && board[i][j] != ' ')
                return board[i][j];
            //判斷副對角線
            else if (board[col - 1 - i][j] == board[col - 2 - i][j + 1] && board[col - 3 - i][j + 2] == board[col - 1 - i][j] && board[col - 1 - i][j] != ' ')
                return board[col - 1 - i][j];
        }
    }
    for (i = 0; i < row - 2; i++)
    {
        for (j = 0; j < col; j++)
        {
            //判斷豎行
            if (board[i][j] == board[i + 1][j] && board[i][j] == board[i + 2][j] && board[i][j] != ' ')
                return board[i][j];
        }
    }

    //判斷是否滿盤---放在最後是因為最後一步的判斷
    if (1 == ISFULL(board, row, col))
    {
      return 'F';
    }

return 'C';
}

到這,我們的三子棋似乎已經編完了,先看一下執行結果:

    

 

 

在這個過程中,我們會發現電腦下的特別快(當然,這跟我們的懶惰有關……)所以我們在電腦下的步驟中加一個Sleep()函式來延長電腦所用時間

已即,我們可以下一次子就清一下屏,這樣看起來比較舒服

所以

最後的程式碼部分:

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>

#define ROW 3//利用巨集來實現棋盤的大小
#define COL 3

void InitBoard(char board[ROW][COL], int row, int col);//棋盤初始化

void DisplayBoard(char board[ROW][COL], int row, int col);//棋盤列印函式

void PlayerMove(char board[ROW][COL], int row, int col);//玩家下棋

void ComputerMove(char board[ROW][COL], int row, int col);//電腦下棋

char ISWIN(char board[ROW][COL], int row, int col);//判斷輸贏

int ISFULL(char board[ROW][COL], int row, int col);//判斷棋盤是否已滿
game.h
#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

void InitBoard(char board[ROW][COL], int row, int col)//棋盤初始化
{
    int i = 0, j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            board[i][j] = ' ';
        }
    }
}

//void DisplayBoard(char board[3][3], int row, int col)//棋盤列印函式
//{
//    for (int i = 0; i < 3; i++)
//    {
//        printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//        if (i < 2)
//        {
//            printf("---|---|---\n");
//        }    
//    }
//}

void DisplayBoard(char board[ROW][COL], int row, int col)//棋盤列印函式
{
    int i = 0, j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            printf(" %c ", board[i][j]);
            if (j < col - 1)
            {
                printf("|");//分割列
            }
        }
        printf("\n");

        if (i < row - 1)
        {
            for (j = 0; j < 3; j++)
            {
                printf("---");//分割行
                if (j < col - 1)
                {
                    printf("|");
                }
            }
        }    
        printf("\n");
    }
}

void PlayerMove(char board[ROW][COL], int row, int col)//玩家下棋
{
    int x, y;
    printf("玩家走:\n");
    while (1)
    {
        printf("請輸入你所要落子的座標:");
        scanf("%d%d", &x, &y);
        if (!((x > 0 && x <= row) && (y > 0 && y <= col)))
        {
            printf("該座標為非法座標,請重新輸入!\n");//座標非法
        }
        else if (board[x - 1][y - 1] != ' ')//座標被佔用
        {
            printf("該座標已被佔用,請重新下子!\n");
        }
        else
        {
            board[x - 1][y - 1] = '*';//玩家落子,暫時用 * 來表示
            break;
        }
    }
    system("cls");
}

void ComputerMove(char board[ROW][COL], int row, int col)//電腦下棋
{
    int x, y;
    printf("電腦走:\n");
    Sleep(1000);
    while (1)
    {
         x = rand() % row;
         y = rand() % col;
         if (board[x][y] == ' ')
         {
             board[x][y] = '#';//這裡我們用 # 來表示電腦下棋
             break;
         }
    }
    system("cls");
}

int ISFULL(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            if (board[i][j] == ' ')
                return 0;
        }
    }
    return 1;
}

char ISWIN(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;


    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col - 2; j++)
        {
            //判斷橫行
            if (board[i][j] == board[i][j + 1] && board[i][j] == board[i][j + 2] && board[i][j] != ' ')
                return board[i][j];
            //判斷主對角線
            else if (board[i][j] == board[i + 1][j + 1] && board[i + 2][j + 2] == board[i][j] && board[i][j] != ' ')
                return board[i][j];
            //判斷副對角線
            else if (board[col - 1 - i][j] == board[col - 2 - i][j + 1] && board[col - 3 - i][j + 2] == board[col - 1 - i][j] && board[col - 1 - i][j] != ' ')
                return board[col - 1 - i][j];
        }
    }
    for (i = 0; i < row - 2; i++)
    {
        for (j = 0; j < col; j++)
        {
            //判斷豎行
            if (board[i][j] == board[i + 1][j] && board[i][j] == board[i + 2][j] && board[i][j] != ' ')
                return board[i][j];
        }
    }

    //判斷是否滿盤---放在最後是因為最後一步的判斷
    if (1 == ISFULL(board, row, col))
    {
        return 'F';
    }
    return 'C';
}
game.c
#define _CRT_SECURE_NO_WARNINGS 1//加這一句話是因為筆者採用的是 VS 編譯器,為了防止一些不必要的錯誤出現

#include "game.h"

void menu()//列出可供玩家選擇的模式
{
    printf("**************************************************************\n");
    printf("*****************         1.play              ****************\n");
    printf("*****************         0.exit              ****************\n");
    printf("**************************************************************\n");
}

void play()
{
    int ret;
    char board[ROW][COL] = { 0 };
    InitBoard(board, ROW, COL);

    DisplayBoard(board, ROW, COL);
    while (1)
    {
        PlayerMove(board, ROW, COL);
        DisplayBoard(board, ROW, COL);
        ret = ISWIN(board, ROW, COL);
        {
            if (ret != 'C')
                break;
        }

        ComputerMove(board, ROW, COL);
        DisplayBoard(board, ROW, COL);
        ret = ISWIN(board, ROW, COL);
        {
            if (ret != 'C')
                break;
        }
    }
    switch (ret)
    {
        case 'F':
            printf("和局!\n");
            system("pause");
            system("cls");
            break;
        case '#':
            printf("電腦贏!\n");
            system("pause");
            system("cls");
            break;
        case '*':
            printf("玩家贏!\n");
            system("pause");
            system("cls");
            break;
        default:
            break;
    }
}


int main()
{
    srand((unsigned)time(NULL));//隨機種子的初始化
    int input;//在這裡,我們利用玩家選擇的模式來控制迴圈的終止
    do
    {
        menu();
        printf("請輸入你的選擇:");
        scanf("%d", &input);
        switch (input)
        {
        case 1://play
            system("cls");
            play();
            break;
        case 0://退出
            printf("歡迎下次再來!\n");
            break;
        default://當玩家輸入了非法字元,讓其重新選擇
            system("cls");
            printf("輸入錯誤,請重新輸入!\n");
        }
    } while (input);//當input為0時,停止迴圈
    
    return 0;
}
test.c

 

三.擴充套件

1.五子棋的實現

五子棋的實現僅僅只改變了判斷規則,其它方式都沒變。

判斷程式碼:

char ISWIN(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;

    
    for (i = 0; i < col; i++)
    {
        for (j = 0; j < col - 4; j++)
        {
            //判斷橫行
            if (board[i][j] == board[i][j + 1] && board[i][j] == board[i][j + 2] && board[i][j] == board[i][j + 3] && board[i][j] == board[i][j + 4] && board[i][j] != ' ')
                return board[i][j];
            //判斷對角線
            else if (board[i][j] == board[i + 1][j + 1] && board[i + 2][j + 2] == board[i][j] && board[i][j] == board[i + 3][j + 3] && board[i][j] == board[i + 4][j + 4] && board[i][j] != ' ')
                return board[i][j];
            else if (board[col - 1 - i][j] == board[col - 2 - i][j + 1] && board[col - 3 - i][j + 2] == board[col - 1 - i][j] && board[col - 4 - i][j + 3] == board[col - 2 - i][j + 1] && board[col - 5 - i][j + 4] == board[col - 2 - i][j + 1] && board[col - 1 - i][j] != ' ')
                return board[col - 1 - i][j];
        }
    }
    for (i = 0; i < col - 4; i++)
    {
        for (j = 0; j < col ; j++)
        {
            //判斷豎行
            if (board[i][j] == board[i + 1][j] && board[i][j] == board[i + 2][j] && board[i][j] == board[i + 3][j] && board[i][j] == board[i + 4][j] && board[i][j] != ' ')
                return board[i][j];
        }
    }
    //判斷是否滿盤
    if (1 == ISFULL(board, row, col))
    {
        return 'f';
    }
    
    return 'c';
}

2.玩家對戰玩家

只需將電腦下子的部分,替換成玩家下子即可

 

完整五子棋-人人對戰程式碼:

#pragma once
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>

#define ROW 10
#define COL 10

//初始化棋盤 
void InitBoard(char board[ROW][COL], int row, int col);

//列印棋盤
void DisplayBoard(char board[ROW][COL], int row, int col);

//玩家走
void Player1Move(char board[ROW][COL],int row, int col);

//玩家2走
void Player2Move(char board[ROW][COL], int row, int col);

//電腦走
void ComputerMove(char board[ROW][COL], int row, int col);

//判斷輸贏
char ISWIN(char board[ROW][COL], int row, int col);
 
//判斷棋盤是否已滿
int ISFULL(char board[ROW][COL], int row, int col);
game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

void InitBoard(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            board[i][j] = ' ';
        }
    }
}

void DisplayBoard(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;
    for (i = 0; i < col; i++)
        printf("   %d", i + 1);
    printf("\n");
    for (i = 0; i < row; i++)
    {
        printf("%2d", i + 1);
        for (j = 0; j < col; j++)
        {
            printf(" %c ",board[i][j]);
            if (j < col - 1)
                printf("|");
        }
        printf("\n");

        //列印分割行
        if (i < row - 1)
        {
            printf("  ");
            for (j = 0; j < col; j++)
            {
                printf("---");
                if (j < col - 1)
                    printf("|");
            }
            printf("\n");
        }
    }
}

void Player1Move(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;
    while (1)
    {
        printf("請輸入玩家1要下的棋的座標:");
        scanf("%d%d", &i, &j);
        //檢查是否越界
        if (1 <= i && i <= row && j >= 1 && j <= col)
        {
            if (board[i-1][j-1] != ' ')
                printf("此座標已被佔用!\n");
            else
            {
                board[i-1][j-1] = '*';
                break;
            }
        }
        else
            printf("座標非法訪問!\n");
    }
    system("cls");
}

void Player2Move(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;
    while (1)
    {
        printf("請輸入玩家2要下的棋的座標:");
        scanf("%d%d", &i, &j);
        //檢查是否越界
        if (1 <= i && i <= row && j >= 1 && j <= col)
        {
            if (board[i - 1][j - 1] != ' ')
                printf("此座標已被佔用!\n");
            else
            {
                board[i - 1][j - 1] = '#';
                break;
            }
        }
        else
            printf("座標非法訪問!\n");
    }
    system("cls");
}

//void ComputerMove(char board[ROW][COL], int row, int col)
//{
//    int i = 0, j = 0;
//    printf("電腦走:\n");
//    while (1)
//    {
//        i = rand() % row;
//        j = rand() % col;
//        if (board[i][j] == ' ')
//        {
//            board[i][j] = '#';
//            break;
//        }
//    }
//    Sleep(1000);
//    system("cls");
//}

int ISFULL(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            if (board[i][j] == ' ')
                return 0;
        }
    }
    return 1;
}

char ISWIN(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0;

    
    for (i = 0; i < col; i++)
    {
        for (j = 0; j < col - 4; j++)
        {
            //判斷橫行
            if (board[i][j] == board[i][j + 1] && board[i][j] == board[i][j + 2] && board[i][j] == board[i][j + 3] && board[i][j] == board[i][j + 4] && board[i][j] != ' ')
                return board[i][j];
            //判斷對角線
            else if (board[i][j] == board[i + 1][j + 1] && board[i + 2][j + 2] == board[i][j] && board[i][j] == board[i + 3][j + 3] && board[i][j] == board[i + 4][j + 4] && board[i][j] != ' ')
                return board[i][j];
            else if (board[col - 1 - i][j] == board[col - 2 - i][j + 1] && board[col - 3 - i][j + 2] == board[col - 1 - i][j] && board[col - 4 - i][j + 3] == board[col - 2 - i][j + 1] && board[col - 5 - i][j + 4] == board[col - 2 - i][j + 1] && board[col - 1 - i][j] != ' ')
                return board[col - 1 - i][j];
        }
    }
    for (i = 0; i < col - 4; i++)
    {
        for (j = 0; j < col ; j++)
        {
            //判斷豎行
            if (board[i][j] == board[i + 1][j] && board[i][j] == board[i + 2][j] && board[i][j] == board[i + 3][j] && board[i][j] == board[i + 4][j] && board[i][j] != ' ')
                return board[i][j];
        }
    }
    //判斷是否滿盤
    if (1 == ISFULL(board, row, col))
    {
        return 'f';
    }
    
    return 'c';
}
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void menu()
{
    printf("*********************************************\n");
    printf("*******             1.play          *********\n");
    printf("*******             0.quit          *********\n");
    printf("*********************************************\n");
}

int choice()
{
    int input = 0;
    printf("請輸入你的選擇:");
    scanf("%d", &input);
    return input;
}

void game()
{
    char ret;
    char board[ROW][COL] = { 0 };
    InitBoard(board, ROW, COL);
    DisplayBoard(board, ROW, COL);
    while (1)
    {
        Player1Move(board, ROW, COL);

        DisplayBoard(board, ROW, COL);

        ret = ISWIN(board, ROW, COL);
        {
            if (ret != 'c')
                break;
        }

        Player2Move(board, ROW, COL);

        DisplayBoard(board, ROW, COL);

        ret = ISWIN(board, ROW, COL);
        {
            if (ret != 'c')
                break;
        }
    }
    switch (ret)
    {
    case 'f':
        printf("和局!\n");
        break;
    case '#':
        //printf("電腦贏!\n");
        printf("玩家2贏!\n");
        break;
    case '*':
        printf("玩家1贏!\n");
        break;
    default:
        break;
    }
}

void play()
{
    int input;
    do 
    {
        menu();
        input = choice();
        system("cls");
        switch (input)
        {
        case 1:
            game();
            break;
        case 0:
            printf("謝謝使用,歡迎下次再來!\n");
            break;
        default:
            system("cls");
            printf("輸入錯誤,請重新輸入!\n");
            break;
        }
    } while (input);
}

int main()
{
    play();
    return 0;
}
test.c

 

3.電腦下棋的探究

對此我們的演算法肯定是需要極大的改進的

建議:

  1.不在用隨機函式代替機器大腦

  2.電腦根據情況堵截(但是這樣的話,最終的情況多是平局)

所以:

  在這,我們還仍需更多的努力!

 

4.將game.c轉為lib檔案

 

然後出現:

 

 此時我們會看到在我們的專案資料夾下的Debug資料夾下產生了:

 

 接著,我們就可刪除我們專案中的game.c了

然後再test.c中加入這樣的一句話:

 

 此時我們再點選執行,發現與之前並無不同!

 

 

5.打包為可安裝檔案

因為在VS2019中,微軟似乎刪除了這個元件,所以我們先需要去官網下載安裝一下 : 點這

直接安裝就行,儘管它是英文版,但是我們任可使用(學好英語的重要性!!!)

 

 

 

關於三子棋的講解便到此為止。

因筆者水平有限,若有錯誤之處,還望多多指正。

&n