C語(yǔ)言實(shí)現(xiàn)三子棋游戲的示例代碼
1. 前言
大家好,我是努力學(xué)習(xí)游泳的魚(yú),今天我們會(huì)用C語(yǔ)言實(shí)現(xiàn)三子棋。所謂三子棋,就是三行三列的棋盤(pán),玩家可以和電腦下棋,率先連成三個(gè)的獲勝。話不多說(shuō),我們開(kāi)始吧。
2. 準(zhǔn)備工作
我們可以在一個(gè)項(xiàng)目中創(chuàng)建三個(gè)文件,分別是:
- test.c,測(cè)試游戲的邏輯。
- game.c,游戲的實(shí)現(xiàn)。
- game.h,函數(shù)聲明,符號(hào)的定義。
測(cè)試這個(gè)游戲時(shí),我們玩一把肯定不過(guò)癮,所以需要使用do while循環(huán),每次可以選擇繼續(xù)玩或者退出游戲。先把大致的框架搭出來(lái)。
#include <stdio.h>
void menu()
{
printf("****************************\n");
printf("******** 1. play ******\n");
printf("******** 0. exit ******\n");
printf("****************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("請(qǐng)選擇(1/0):>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("三子棋\n");
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("選擇錯(cuò)誤\n");
break;
}
} while (input);
return 0;
}

當(dāng)然,我們的游戲不可能只是打印“三子棋”這三個(gè)字這么簡(jiǎn)單,具體的實(shí)現(xiàn)我們會(huì)封裝成一個(gè)函數(shù),暫且取名為game。
3. 使用二維數(shù)組存儲(chǔ)下棋的數(shù)據(jù)
當(dāng)我們下三子棋的時(shí)候,需要把下棋的數(shù)據(jù)存起來(lái)。由于三子棋的棋盤(pán)是3×3的,我們就需要一個(gè)三行三列的數(shù)組來(lái)存儲(chǔ)下棋的數(shù)據(jù)。char board[3][3] = { 0 };
4. 初始化棋盤(pán)為全空格
當(dāng)我們還沒(méi)開(kāi)始下棋時(shí),棋盤(pán)上應(yīng)該啥都沒(méi)有,但是真的是啥都沒(méi)有嗎?事實(shí)上,如果我們打印棋盤(pán)時(shí),能打印出“沒(méi)有棋子”的效果,數(shù)組里應(yīng)該是全空格。所以,我們需要寫(xiě)一個(gè)函數(shù),初始化棋盤(pán)為全空格。InitBoard(board, 3, 3);這個(gè)函數(shù)的聲明,我們會(huì)放在game.h里。具體的實(shí)現(xiàn),我們會(huì)放在game.c里。以下的函數(shù)同理。該函數(shù)的聲明:void InitBoard(char board[3][3], int row, int col);(以下的函數(shù)均省略聲明)。該函數(shù)的實(shí)現(xiàn),只需遍歷這個(gè)二維數(shù)組,全部賦值為空格。
void InitBoard(char board[3][3], int row, int col)
{
int i = 0;
for (; i < row; ++i)
{
int j = 0;
for (; j < col; ++j)
{
board[i][j] = ' ';
}
}
}
有沒(méi)有發(fā)現(xiàn),這個(gè)程序中,到處都要用到數(shù)組“三行三列”這個(gè)特點(diǎn)。如果我們想要改變這一點(diǎn),比如改成五行五列,就需要改很多地方,非常麻煩。怎么解決這個(gè)問(wèn)題呢?我們可以在game.h里定義兩個(gè)常量ROW和COL,這樣每次只需要改變#define后的值就行了。
#define ROW 3 #define COL 3
這樣以上出現(xiàn)的所有3都可以用ROW和COL替代了(此處省略)。注意:如果想使用game.h里的符號(hào),我們需要在game.c和test.c里引用這個(gè)頭文件。引用自己寫(xiě)的頭文件要用雙引號(hào)。#include "game.h"而對(duì)于其他頭文件如stdio.h,我們不需要在game.c和test.c里包含兩次,只需在game.h里包含就行了。
5. 打印棋盤(pán)
我們初始化棋盤(pán)后,會(huì)想要看一看棋盤(pán)長(zhǎng)啥樣,所以接下來(lái)寫(xiě)一個(gè)打印棋盤(pán)的函數(shù)DisplayBoard(board, ROW, COL);
如果你認(rèn)為打印出數(shù)組的數(shù)據(jù)就行了,從而這樣寫(xiě):
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (; i < row; ++i)
{
int j = 0;
for (; j < col; ++j)
{
printf("%c", board[i][j]);
}
printf("\n");
}
}
實(shí)際運(yùn)行時(shí),你會(huì)發(fā)現(xiàn),打印了,但沒(méi)完全打印。

事實(shí)上,此時(shí)打印的是一堆空格,非常難看。如果我們想打印得好看點(diǎn),可以考慮加上一些橫向和豎向的分割。比如我設(shè)想了這樣一種打印的效果:
| | ---|---|--- | | ---|---|--- | |
假設(shè)把
| | ---|---|---
當(dāng)成一組,總共需要打印3組,為什么呢?因?yàn)橛?行。
每一組里面,又分為數(shù)據(jù)行和分割行。我們需要先打印數(shù)據(jù)行,再打印分割行。
| | // 數(shù)據(jù)行 ---|---|--- // 分割行
所以一個(gè)簡(jiǎn)單的想法是這么寫(xiě):
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (; i < row; ++i)
{
// 打印數(shù)據(jù)
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
// 打印分割的行
printf("---|---|---\n");
}
}
效果如下:

我們發(fā)現(xiàn)多打印了一行分割行,所以打印分割行時(shí)要加一條判斷,不是最后一行才打印分割行。
if (i < row - 1)
printf("---|---|---\n");
這樣子打印就好看多了。

但是這么寫(xiě)的話,就相當(dāng)于,一行一定要打印3列,后面就沒(méi)有改變的可能了。比如我把ROW和COL都改成5,效果如下:

所以這種寫(xiě)法還是不夠好。最好的寫(xiě)法是,再用一層循環(huán)控制列的打印。比如對(duì)于數(shù)據(jù)行:
| |
我們可以把
|
當(dāng)成一組數(shù)據(jù),打印三組,最后一組就不用加上|了(同上一種寫(xiě)法用一個(gè)if語(yǔ)句來(lái)控制)。
對(duì)于分割行
---|---|---
我們可以把
---|
當(dāng)成一組數(shù)據(jù),打印三組,最后一組就不用加上|了(還是用if語(yǔ)句來(lái)控制)。
實(shí)現(xiàn)如下:
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (; i < row; ++i)
{
// 打印數(shù)據(jù)
int j = 0;
for (; j < col; ++j)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
// 打印分割的行
if (i < row - 1)
{
for (j = 0; j < col; ++j)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
效果也沒(méi)問(wèn)題:

若把ROW和COL改成10,打印出來(lái)的效果如下:

這樣寫(xiě)出來(lái)的代碼就比較通用,具有比較強(qiáng)的可維護(hù)性。
6. 玩家下棋
接下來(lái)寫(xiě)玩家下棋的函數(shù)。player_move(board, ROW, COL);
先讓玩家輸入坐標(biāo),若x和y都在1到3之間,則輸入的坐標(biāo)合法,在數(shù)組對(duì)應(yīng)的位置是board[x-1][y-1],若該位置仍然是空格,則這個(gè)位置沒(méi)有被下過(guò),就把數(shù)組的這個(gè)元素改成*。由于若玩家輸入的坐標(biāo)非法或者被占用時(shí),需要重新輸入,故需要一個(gè)循環(huán)。
void player_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋\n");
while (1)
{
printf("請(qǐng)輸入坐標(biāo):>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
// 下棋
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("該坐標(biāo)被占用,請(qǐng)重新輸入\n");
}
}
else
{
printf("坐標(biāo)非法,請(qǐng)重新輸入\n");
}
}
}
玩家下棋后再把棋盤(pán)打印一下DisplayBoard(board, ROW, COL);效果如下:

7. 電腦下棋
玩家下完棋后就輪到電腦下棋computer_move(board, ROW, COL);
我們讓電腦隨機(jī)下棋,只需生成兩個(gè)隨機(jī)的坐標(biāo)x和y,若該位置是空格,就下在這個(gè)位置,如果是空格,那就再次產(chǎn)生隨機(jī)坐標(biāo),可見(jiàn),這也需要一個(gè)循環(huán)來(lái)實(shí)現(xiàn)。
void computer_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("電腦下棋\n");
while (1)
{
x = rand() % row; // 0~2
y = rand() % col; // 0~2
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
使用rand函數(shù)前需要使用srand函數(shù)來(lái)設(shè)置隨機(jī)數(shù)生成器的起點(diǎn)。我們需要給srand函數(shù)傳一個(gè)時(shí)間戳,這就要用到time函數(shù)。srand((unsigned int)time(NULL));使用rand和srand都需要引用頭文件stdlib.h,使用time函數(shù)需要引用頭文件time.h。
我們用一個(gè)循環(huán),就能實(shí)現(xiàn)玩家和電腦輪流下棋的效果。
while (1)
{
// 玩家下棋
player_move(board, ROW, COL);
DisplayBoard(board, ROW, COL);
// 電腦下棋
computer_move(board, ROW, COL); // 隨機(jī)下棋
DisplayBoard(board, ROW, COL);
}
8. 判斷輸贏
什么時(shí)候游戲就結(jié)束了呢?如果玩家贏了,或者電腦贏了,或者平局,游戲就結(jié)束了,否則游戲繼續(xù)。
我們來(lái)設(shè)計(jì)一個(gè)is_win函數(shù)來(lái)判斷棋局是上面四種狀態(tài)的哪一種。我們這么設(shè)計(jì)is_win函數(shù)的返回值:
- 玩家贏 - ‘*’
- 電腦贏 - ‘#’
- 平局 - ‘Q’
- 繼續(xù) - ‘C’
當(dāng)棋局狀態(tài)不是C時(shí)說(shuō)明游戲結(jié)束了,就跳出循環(huán),接著根據(jù)不同情況打印不同的結(jié)果。
char ret = 0;
while (1)
{
// 玩家下棋
player_move(board, ROW, COL);
DisplayBoard(board, ROW, COL);
// 判斷輸贏
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
// 電腦下棋
computer_move(board, ROW, COL); // 隨機(jī)下棋
DisplayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("玩家贏\n");
}
else if (ret == '#')
{
printf("電腦贏\n");
}
else
{
printf("平局\n");
}
如何實(shí)現(xiàn)is_win函數(shù)呢?只需判斷每行,每列,每條對(duì)角線是否有三個(gè)同樣的棋子就行了。
先判斷行(由于玩家贏和電腦贏都是返回對(duì)應(yīng)的棋子——*和#,所以直接把對(duì)應(yīng)的數(shù)組元素返回就行了。):
int i = 0;
for (; i < row; ++i)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{
return board[i][1];
}
}
判斷列同理:
for (i = 0; i < col; ++i)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
{
return board[1][i];
}
}
最后判斷對(duì)角線:
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
如果有人贏了,在前面的代碼中就會(huì)返回。如果沒(méi)人贏,再來(lái)判斷是否平局。如果棋盤(pán)沒(méi)有空格了,那就是平局。我們可以寫(xiě)一個(gè)is_full函數(shù)來(lái)判斷棋盤(pán)是否滿了。如果滿了就返回1,沒(méi)滿就返回0。
由于is_full函數(shù)只是寫(xiě)給is_win函數(shù)的,只需要在game.c這個(gè)文件內(nèi)使用,所以加上static。具體的實(shí)現(xiàn),只需要遍歷數(shù)組,若發(fā)現(xiàn)空格,說(shuō)明棋盤(pán)沒(méi)滿,就返回0,否則返回1。
而如果沒(méi)人贏,也不是平局,則游戲繼續(xù),return 'C';即可。
9. 效果展示
寫(xiě)到這,我們就把三子棋程序?qū)懲昀?。接下?lái)看看效果:
玩家贏:

電腦贏:

平局:

10. 完整代碼
下面是完整的代碼:
game.h
#pragma once #include <stdio.h> #include <stdlib.h> #include <time.h> #define ROW 3 #define COL 3 // 初始化棋盤(pán) void InitBoard(char board[ROW][COL], int row, int col); // 打印棋盤(pán) 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);
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (; i < row; ++i)
{
int j = 0;
for (; j < col; ++j)
{
board[i][j] = ' ';
}
}
}
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// for (; i < row; ++i)
// {
// int j = 0;
// for (; j < col; ++j)
// {
// printf("%c", board[i][j]);
// }
// printf("\n");
// }
//}
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// for (; i < row; ++i)
// {
// // 打印數(shù)據(jù)
// printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
// // 打印分割的行
// if (i < row - 1)
// printf("---|---|---\n");
// }
//}
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (; i < row; ++i)
{
// 打印數(shù)據(jù)
int j = 0;
for (; j < col; ++j)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
// 打印分割的行
if (i < row - 1)
{
for (j = 0; j < col; ++j)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
void player_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋\n");
while (1)
{
printf("請(qǐng)輸入坐標(biāo):>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
// 下棋
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("該坐標(biāo)被占用,請(qǐng)重新輸入\n");
}
}
else
{
printf("坐標(biāo)非法,請(qǐng)重新輸入\n");
}
}
}
void computer_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("電腦下棋\n");
while (1)
{
x = rand() % row; // 0~2
y = rand() % col; // 0~2
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
static int is_full(char board[ROW][COL], int row, int col)
{
int i = 0;
for (; i < row; ++i)
{
int j = 0;
for (; j < col; ++j)
{
if (board[i][j] == ' ')
{
return 0; // 沒(méi)有滿
}
}
}
return 1; // 滿了
}
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0;
// 判斷行
for (; i < row; ++i)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{
return board[i][1];
}
}
// 判斷列
for (i = 0; i < col; ++i)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
{
return board[1][i];
}
}
// 對(duì)角線
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
// 判斷平局
if (is_full(board, row, col) == 1)
{
return 'Q';
}
// 繼續(xù)
return 'C';
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("****************************\n");
printf("******** 1. play ******\n");
printf("******** 0. exit ******\n");
printf("****************************\n");
}
void game()
{
// 三子棋小游戲的具體實(shí)現(xiàn)
char ret = 0;
// 存放下棋的數(shù)據(jù)
char board[ROW][COL] = { 0 };
// 初始化棋盤(pán)為全空格
InitBoard(board, ROW, COL);
// 打印棋盤(pán)
DisplayBoard(board, ROW, COL);
while (1)
{
// 玩家下棋
player_move(board, ROW, COL);
DisplayBoard(board, ROW, COL);
// 判斷輸贏
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
// 電腦下棋
computer_move(board, ROW, COL); // 隨機(jī)下棋
DisplayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("玩家贏\n");
}
else if (ret == '#')
{
printf("電腦贏\n");
}
else
{
printf("平局\n");
}
//DisplayBoard(board, ROW, COL);
}
//
// 什么時(shí)候,游戲就結(jié)束了
// 玩家贏 - '*'
// 電腦贏 - '#'
// 平局 - 'Q'
// 繼續(xù) - 'C'
//
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("請(qǐng)選擇(1/0):>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("選擇錯(cuò)誤\n");
break;
}
} while (input);
return 0;
}
到此這篇關(guān)于C語(yǔ)言實(shí)現(xiàn)三子棋游戲的示例代碼的文章就介紹到這了,更多相關(guān)C語(yǔ)言三子棋游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VSCODE調(diào)試RDKit內(nèi)核的方法步驟(C++)
本文主要介紹了VSCODE調(diào)試RDKit內(nèi)核的方法步驟,這個(gè)過(guò)程可以分為三個(gè)部分:安裝 RDKit 所需環(huán)境,安裝 VSCode 相應(yīng)插件, 寫(xiě)調(diào)試代碼編譯,感興趣的可以了解一下2021-08-08
C++使用grpc實(shí)現(xiàn)回射服務(wù)器
gRPC是由Google開(kāi)發(fā)的一個(gè)開(kāi)源的高性能遠(yuǎn)程過(guò)程調(diào)用(RPC)框架,用于在分布式系統(tǒng)中實(shí)現(xiàn)跨語(yǔ)言的服務(wù)通信,本文我們就來(lái)看看C++如何使用grpc實(shí)現(xiàn)回射服務(wù)器2024-10-10
C++產(chǎn)生隨機(jī)數(shù)的幾種方法小結(jié)
本文主要介紹了C++產(chǎn)生隨機(jī)數(shù)的幾種方法小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
Qt實(shí)現(xiàn)轉(zhuǎn)動(dòng)輪播圖
這篇文章主要為大家詳細(xì)介紹了Qt實(shí)現(xiàn)轉(zhuǎn)動(dòng)輪播圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
C++實(shí)現(xiàn)超市商品管理系統(tǒng)最新版
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)超市商品管理系統(tǒng)最新版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
C++實(shí)現(xiàn)高校人員信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)高校人員信息管理系統(tǒng)項(xiàng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
C++學(xué)了這么多年你知道為什么定義類(lèi)時(shí),類(lèi)的定義放在.h文件中,而類(lèi)的實(shí)現(xiàn)放在cpp文件中。它們?yōu)槭裁茨軌蜿P(guān)聯(lián)到一起呢?你知道什么東西可以放在.h文件中,什么不能。什么東西又可以放在cpp文件中。如果你忘記了或是壓根就不明白,那么讀過(guò)此文你會(huì)清晰無(wú)比?。?/div> 2014-09-09最新評(píng)論

