我們知道 生活中 孤獨寂寞的時候很多,既然我們手中已經掌握了編程的技術,那為何不利用我們的技能創建 一個智能Al來陪我們對弈下棋,讓我們不再孤獨呢?
于是這款 AI三子棋就因需而生了
這篇博客將手把手教會你 自己解決孤獨!
1.主界面 詢問是否孤獨

void Main_menu() {
printf("*************************\n");
printf("******1. 我很孤獨********\n");
printf("******0. 我不孤獨********\n");
printf("*************************\n");
printf("******2. 我不理解********\n");
}
暖心AI詢問是否孤獨 ,來決定是否 幫助玩家派遣孤獨
這里明顯需要 用到選擇結構 我們這里用switch陳述句來實作
代碼:
int main() {
srand((unsigned int)time(NULL)); 這是待會要用到的 亂數的初始化 暫時先不管
int input; 用來儲存玩家選的的序號
do {
Main_menu();
printf("請選擇序號:>");
scanf("%d", &input); 存放玩家選擇的序號
switch (input) {
case 1: /如果你很孤獨,AI就會陪伴你
printf("\n既然你很孤獨,我們來下棋吧!\n");
printf("\n需要看規則嗎?\n\n"); /暖心AI的望聞問切
printf("1. 需要,我不知道怎么下\n");
printf("2. 不需要,我是高手\n");
int tmp;/用來儲存這次選擇的序號
scanf(" %d", &tmp);
switch (tmp) {
case 1:
INFOR();/撰寫好的 資訊模塊,里面有游戲規則和其他資訊,在文章后面我會有講解
break;
case 2:
printf("請:>\n");
game();/直接進入游戲模塊
break;
}
break;
case 0: /不孤獨的話就不要浪費時間了
printf("不孤獨就退出程式");
break;
case 2: /玩家 表示不理解 AI耐心的解釋
printf("\n這是一款 能在孤獨時陪你下棋的溫暖AI!!!\n\n");
break;
default:
printf("選擇序號有誤,請重新選擇>\n");
}
} while (input != 0);/當玩家選擇0 就會退出
return 0;
}
根據 玩家不同的選擇 進入到不同的模塊
當玩家選擇 0,表示他并不孤獨:

AI表示很無語,顯然它不想浪費時間
當玩家選擇 2,表示他不理解:

AI會很耐心的像玩家解釋 這是什么,
AI詢問玩家 是否需要了解規則
當玩家選擇 1,表示他很孤獨:

暖心AI會貼心的提出一起下棋的建議,并貼心的詢問玩家是否 熟悉三子棋游戲的規則,
當玩家選擇 1,他不知道規則:

這時就會進入我們撰寫好的 資訊模塊 里面有游戲規則和智能AI的說明資訊
首先我們把 游戲規則和AI資訊等需要說明的內容都封裝到一個源檔案里,在把函式的定義寫到一個頭檔案里
源檔案內容:
#include"menu.h"/引上頭檔案
void Infor_menu() {
printf("1.三子棋游戲規則\n");
printf("2.游戲簡介\n");
printf("0.都懂了(EXIT)\n");
}
void rules() {
printf("1. 下棋走子 需要輸入棋盤坐標,輸入時用空格隔開\n");
printf("如 想在棋盤第一行第一列走子:應輸入 \"1 1\" ; 三子一線即勝利:>\n");
printf("\n");
}
void infor() {
printf("該游戲的對手是凝聚了作者 目前所有的編程智慧結晶的AI副本\n");
printf("原本難度極大,為了派遣你的孤獨和保護你的心理健康故意放水\n若是不放水,難免日后留下難以消除的心理陰影 :>\n");
printf("\n");
}
頭檔案內容:
#pragma once
#include<stdio.h>
/主選單
void Main_menu();
/資訊選單
void Infor_menu();
/規則選單
void rules();
/游戲簡介
void infor();
然后再資訊模塊的函式中 根據玩家的選擇 呼叫對應的函式,玩家想得到的資訊就會被列印出來
資訊模塊代碼
void INFOR() {
int input = 1;
while (input) {
Infor_menu();/提示玩家選擇對應的序號
printf("請選擇序號:");
scanf("%d", &input);
printf("\n");
switch (input) {
case 1:rules();/查看規則
break;
case 2:infor();/AI資訊
break;
case 0:
printf("都懂了就來下棋吧!\n");/進入游戲
game();
break;
default:
printf("輸入無效,請重新輸入\n");
}
}
}
因為是回圈體 所有玩家可以 依次查看不同的資訊 ,最后進入游戲,也就是呼叫game函式,


開始下棋,基情對弈
1.游戲主體
下棋是怎么實作的呢?
不多說,先瀏覽一下代碼,我會再把每一部分拿出來慢慢說明:
void game() {
/儲存資料 二維陣列
char Board[ROW][COL];
/初始化棋盤 - 初始化為空格
InitBoard(Board,ROW,COL);
/接受游戲狀態資訊
char A ;
/列印棋盤 — 列印陣列內容
DisplayBoard(Board, ROW, COL);
進入游戲 你一步 我一步 直到游戲結束
while(1) {
/玩家走- 實作賦值的函式
Player_Move(Board, ROW, COL);
/走完列印棋盤
DisplayBoard(Board, ROW, COL);
/判斷游戲狀態
A = Is_win(Board, ROW, COL);/判斷游戲狀態的函式
if (A != 'C')/如果棋盤未滿 還沒有分出勝負就會回傳字符c
break;/如果不是c就結束游戲
/AI走
Computer_Move(Board, ROW, COL);
/AI思考很快 為了不讓玩家自卑故意等一下再走棋
Sleep(1000);
/走完列印棋盤
DisplayBoard(Board, ROW, COL);
/判斷游戲狀態
A = Is_win(Board, ROW, COL);
if (A != 'C')
break;
}
當游戲結束 查看是平局了還是一方贏了
/判斷游戲狀態的函式會根據游戲的狀態回傳不同的值
switch (A) {/列印出對應的結果后,回傳到主界面 詢問是否還是孤獨
case 'P':
Sleep(1000);
printf("平局了,再來一把吧!\n");
break;
case'X':
Sleep(1000);
printf("你贏了,好厲害!\n");
break;
case'O':
Sleep(1000);
printf("電腦贏了,笨蛋!\n");
break;
}
}
很清楚的是,我們還是通過 把游戲需要用到的動作封裝好 再依次呼叫,這樣就能很流暢的進行玩家和智能AI的對弈
2.功能封裝
所以我們依舊是把游戲里的函式定義放到一個源檔案,函式宣告放到一個頭檔案中
頭檔案代碼:
#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 Player_Move(char Board[ROW][COL], int row ,int col);
/電腦走- 限制隨機 賦值的函式
void Computer_Move(char Board[ROW][COL], int row, int col);
/判斷是否需要結束游戲
char Is_win(char Board[ROW][COL], int row, int col);
/判斷棋盤是否滿了的函式
int Is_full(char Board[ROW][COL], int row, int col);
源檔案代碼:
#include"game.h" /引上頭檔案
完成函式的定義
初始化棋盤 先將陣列的元素都設為0
void InitBoard(char Board[ROW][COL], int row, int col) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++)
Board[i][j] = ' ';
}
}
列印函式
void DisplayBoard(char Board[ROW][COL], int row, int col) {
int r;
int c;
for (r = 0; r < row; r++) {
for (c = 0; c < col; c++) {
printf(" % c ", Board[r][c]);
if (c + 1 < col)
printf("|");
}
printf("\n");
printf("---|---|---\n");
}
}
玩家下棋的函式
void Player_Move(char Board[ROW][COL], int row, int col) {
printf("玩家走:>");
int ro, co;
while (1) {
scanf("%d %d", &ro, &co);
if (ro <= row && co <= col && ro > 0 && co > 0 && Board[ro - 1][co - 1] == ' ') {
Board[ro - 1][co - 1] = 'X';
break;
}
else {
if (!(ro <= row && co <= col && ro > 0 && co > 0))
{
printf("輸錯了 重新輸入:>\n");
}
else
{
printf("這個點(%d,%d)已經有棋子了,不許疊羅漢\n",ro,co);
}
DisplayBoard(Board, row, col);
}
}
}
人工智能AI下棋的函式
void Computer_Move(char Board[ROW][COL], int row, int col) {
int ro, co;
printf("AI走:>\n");
while (1) {
ro = rand() % row;
co = rand() % col;
if (Board[ro][co] == ' ') {
Board[ro][co] = 'O';
break;
}
}
}
判斷棋盤是否下滿的函式
int Is_full(char Board[ROW][COL], int row, int col) {
int i = row;
int j = col;
int flag = 1;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
if (Board[i][j] == ' ')
flag = 0;
}
}
return flag;
}
判斷輸贏的函式
char Is_win(char Board[ROW][COL], int row, int col) {
int i;
int j;
int flag;
flag= 0;
for (i = 0; i < row; i++, flag = 0) {
for (j = 1; j < col; j++) {
if (' ' == Board[i][j - 1] || ' ' == Board[i][j]) {
continue;
}
if (Board[i][j] == Board[i][j-1]) {
flag++;
if (flag == 2)
return Board[i][j];
}
}
}
for (j = flag = 0; j < col; j++, flag = 0) {
for (i = 1; i < row; i++) {
if (' ' == Board[i-1][j] || ' ' == Board[i][j]) {
continue;
}
if (Board[i][j] == Board[i-1][j])
flag++;
if (2 == flag)
return Board[i][j];
}
}
for (i = 1,flag = 0; i < row; i++) {
if ((Board[i][i] == Board[i - 1][i - 1])&&Board[i][i]!=' ') {
flag++;
}
if (2==flag) {
return Board[i][i];
}
}
for (i = 1, j = 1, flag = 0; i < row&&j>=0; i++,j--) {
//0,2 //1,1 //2,0
if ((Board[i][j] == Board[i - 1][j + 1]) && Board[i][j] != ' ') {
flag++;
}
if (2 == flag) {
return Board[i][j];
}
}
if(Is_full(Board,row,col)){
return 'P';
}
return 'C';
}
接下來我們就來逐一分析 這些功能是怎么實作的
(1) 初始化棋盤的函式
我們下棋就是不斷地修改 一個二維陣列(棋盤)的值,所以第一步當然是創建好一個 二維陣列
將它的值都賦為 空格,
初始化棋盤 先將陣列的元素都設為0
void InitBoard(char Board[ROW][COL], int row, int col) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++)
Board[i][j] = ' ';
}
}
(2) 列印棋盤的函式
接下來就是列印棋盤了,我們并不是單純是將 陣列的每一個元素列印出來,而是要耍一點花樣,
列印出棋盤該有的樣子:

列印棋盤的函式
void DisplayBoard(char Board[ROW][COL], int row, int col) {
int r;
int c;
for (r = 0; r < row; r++) {
for (c = 0; c < col; c++) {
printf(" % c ", Board[r][c]);/ 列印空格 字符 空格
if (c + 1 < col)
printf("|");/每個格子用|符號隔開
}
printf("\n");/換行
printf("---|---|---\n");/每行之間加一行間隔
}
}
(3)玩家下棋的函式
玩家下棋 的程序由三個程序組成:>
1. 玩家輸入行 和 列
2.判斷玩家輸入的行列是否在棋盤的范圍內
3.判斷這個點是否已經下過,若已經下過,便溫馨給予提示,重新輸入
4.若該行列合法,將玩家輸入的行列都減一,就得到了該點在二維陣列里的下標,把這個值賦值為字符X
5.最后一部,列印賦值后的棋盤,玩家下棋的這個程序就完畢了

玩家下棋的函式
void Player_Move(char Board[ROW][COL], int row, int col) {
printf("玩家走:>");
int ro, co;
while (1) {
scanf("%d %d", &ro, &co);
if (ro <= row && co <= col && ro > 0 && co > 0 && Board[ro - 1][co - 1] == ' ') {
Board[ro - 1][co - 1] = 'X';
break;
}
else {
if (!(ro <= row && co <= col && ro > 0 && co > 0))
{
printf("輸錯了 重新輸入:>\n");
}
else
{
printf("這個點(%d,%d)已經有棋子了,不許疊羅漢\n",ro,co);
}
DisplayBoard(Board, row, col);成功賦值后 列印棋盤
}
}
}
(4)AI下棋的函式
我們創建的這個人工智能AI是來陪玩家下棋的,自然不能太聰明,若是把玩家弄自閉了,不但起不到陪伴的作用,玩家怒火中燒,結局可能會很難收場,于是乎我們通過亂數的方式,讓我們的暖心AI的智能恰到好處
我們這樣來實作:
1.生成兩個隨機值
2.分別對棋盤的最大行,列取模,這樣就能直接組成一個二維陣列范圍內的下標
3.判斷這個點是否被下過,若已經被下過,則重新生成
4.若沒有被下過,賦值
5.列印

AI下棋的函式
void Computer_Move(char Board[ROW][COL], int row, int col) {
int ro, co;/用來裝AI要下的行列
printf("AI走:>\n");
while (1) {/依舊用回圈結構,成功賦值則跳出
ro = rand() % row;/亂數對棋盤的行取模,得到一個棋盤范圍的行號
co = rand() % col;/同理對棋盤的列取模,得到一個棋盤范圍的列號
if (Board[ro][co] == ' ') {/判斷是否沒下過,若這個點已經下過,則重新生成
Board[ro][co] = 'O';/賦值
break;跳出
}
}
}
有一點要注意的是,為了避免每一次下出現一樣的亂數,我們在main函式的第一句,用時間戳來初始化我們的亂數生成,使每一次產生的亂數都不一樣:
int main() {
srand((unsigned int)time(NULL));
(5)判斷棋盤是否下滿的函式
我們知道三子棋有三種結果:
1.玩家贏
2.AI贏
3.棋盤下滿了,都沒贏,也就是平局的結果
所以我們等會兒判斷游戲輸贏的函式中會呼叫這個函式來看是平局還是沒下完,要接著對弈,
判斷棋盤是否下滿的函式
int Is_full(char Board[ROW][COL], int row, int col) {
int i = row;
int j = col;
int flag = 1;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
if (Board[i][j] == ' ')
flag = 0;
}
}
return flag;
}
我們把每個點都遍歷一遍,如果有空格,表明沒滿,回傳0,
如果沒有空格,表明棋盤滿了,回傳1.
(6)判斷游戲輸贏的函式
我們在對弈中,每落子一次,我們就判斷一次輸贏(呼叫一次這個函式),只有在雙方都沒贏且棋盤沒有滿的情況下,才繼續下棋,
具體這樣實作:
1.判斷 每行有沒有出現三個連續相同的字符(空格除外),若有退出,回傳這個字符的值(我們暖心AI和玩家的字符時不一樣的),若沒有?
2.判斷 每列有沒有出現三個連續相同的字符(空格除外),若有退出,回傳這個字符的值(我們暖心AI和玩家的字符時不一樣的),若沒有?
3.判斷 左對角線……
4.判斷 右對角線……
經過以上四步,表明沒有三個連續相同的棋子,那么就剩兩種情況了:
i.棋盤沒滿,還未分出勝負,接著下棋
ii.棋盤滿了,平局,退出游戲
這時我們就要用到上面的 判斷棋盤是否已滿的函式了?
5.判斷棋盤是否下滿,下滿回傳字符P表示平局,未下滿回傳C表示未分出勝負 接著下,
需要注意的是這個函回傳的字符表示呼叫時游戲的輸贏狀態,呼叫它的函式會根據回傳的值來進行操作
代碼如下:
我們定義一個flag變數來數相同字符的數量
char Is_win(char Board[ROW][COL], int row, int col) {
int i;
int j;
int flag;
flag= 0;
for (i = 0; i < row; i++, flag = 0) {
for (j = 1; j < col; j++) {
if (' ' == Board[i][j - 1] || ' ' == Board[i][j]) {
continue;
}
if (Board[i][j] == Board[i][j-1]) {
flag++;
if (flag == 2)
return Board[i][j];
}
}
}
for (j = flag = 0; j < col; j++, flag = 0) {
for (i = 1; i < row; i++) {
if (' ' == Board[i-1][j] || ' ' == Board[i][j]) {
continue;
}
if (Board[i][j] == Board[i-1][j])
flag++;
if (2 == flag)
return Board[i][j];
}
}
for (i = 1,flag = 0; i < row; i++) {
if ((Board[i][i] == Board[i - 1][i - 1])&&Board[i][i]!=' ') {
flag++;
}
if (2==flag) {
return Board[i][i];
}
}
for (i = 1, j = 1, flag = 0; i < row&&j>=0; i++,j--) {
//0,2 //1,1 //2,0
if ((Board[i][j] == Board[i - 1][j + 1]) && Board[i][j] != ' ') {
flag++;
}
if (2 == flag) {
return Board[i][j];
}
}
if(Is_full(Board,row,col)){
return 'P';
}
return 'C';
}
通過這樣的流程,可以實作每一方落完子都能獲取到當前游戲的輸贏狀態,
這時我們再回到游戲主體函式,進入對弈之后的代碼
進入游戲 你一步 我一步 直到游戲結束
while(1) {
/玩家走- 實作賦值的函式
Player_Move(Board, ROW, COL);
/走完列印棋盤
DisplayBoard(Board, ROW, COL);
/判斷游戲狀態
A = Is_win(Board, ROW, COL);/判斷游戲狀態的函式
if (A != 'C')/如果棋盤未滿 還沒有分出勝負就會回傳字符c
break;/如果不是c就結束游戲
/AI走
Computer_Move(Board, ROW, COL);
/AI思考很快 為了不讓玩家自卑故意等一下再走棋
Sleep(1000);
/走完列印棋盤
DisplayBoard(Board, ROW, COL);
/判斷游戲狀態
A = Is_win(Board, ROW, COL);
if (A != 'C')
break;
}
當游戲結束 查看是平局了還是一方贏了
/判斷游戲狀態的函式會根據游戲的狀態回傳不同的值
switch (A) {/列印出對應的結果后,回傳到主界面 詢問是否還是孤獨
case 'P':
Sleep(1000);
printf("平局了,再來一把吧!\n");
break;
case'X':
Sleep(1000);
printf("你贏了,好厲害!\n");
break;
case'O':
Sleep(1000);
printf("電腦贏了,笨蛋!\n");
break;
}
}
不難發現,這樣一來
只有判斷輸贏的函式回傳C才繼續下,不然就跳出,跳出后再進入一個switch陳述句來看具體是那種結局
結尾
相信通過通過以上的分析,這個暖心AI陪下三子棋的流程就能很清晰的掌握了,希望這篇博客能對大家的代碼能力和心理健康起到積極的影響,希望在你一個人如孤狼般奮進,默默成為大牛的路上,有一這位人工智能陪你下三子棋,撫慰你寂寞的心,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/375040.html
標籤:其他
上一篇:【Pygame系列】別樣的飛機大戰:太空隕石VS大型戰機對決(內含原始碼)
下一篇:決議ET6框架熱多載的實作
