C語言三子棋遊戲實現及AI設想、五子棋推廣
技術標籤:C學習
Part 1 三子棋遊戲基礎實現:
1、操作介面實現:
即實現供使用者選擇的的選單:我們定義為menu函式
void menu(){
printf("****************\n");
printf("*****1.play*****\n");
printf("*****0.exit*****\n");
printf("****************\n");
}
我們希望達到的目的是使用者可以不斷選擇,直至按0退出遊戲,於是我們在主函式中需要搭建框架:
int main(){
srand((unsigned int)time(NULL));//為後函式中rand提供起點
int input = 0;
do{
menu();
printf("請選擇:>\n");
scanf("%d", &input);
switch(input){
case 1:
printf("遊戲開始\n");
game();//遊戲的核心函式
break;
case 0:
printf("退出遊戲!\n");
break;
default:
printf("輸入錯誤,請重新輸入\n");
break;
}
} while (input);//巧妙利用input實現中止迴圈
return 0;
}
2、遊戲核心函式實現game()函式:
在進入遊戲時我們需要的變數有board二維陣列,和ret變數(判斷輸贏)
1、InitBoard函式在每次game()函式開始初始化棋盤
2、DisplayBoard函式 視覺化棋盤
3、PlayerMove函式即玩家輸入座標實現下棋操作
4、ComputerMove函式電腦隨機生成座標下棋
5、CheckWin函式,無論玩家或是電腦走棋後,判斷當前輸贏
void game(){
char board[ROW][COL];//ROW,COL通過巨集定義,三子棋中即為3行3列
char ret = 0;//判斷輸贏
InitBoard(board, ROW, COL);//初始化棋盤
DisplayBoard(board, ROW, COL);//視覺化棋盤
while(1){
PlayerMove(board, ROW, COL);//玩家輸入座標實現下棋操作
ret = CheckWin(board, ROW, COL);//玩家或是電腦走棋後,判斷當前輸贏
if(ret !='C'){
break;
}
DisplayBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);//電腦隨機生成座標下棋
ret = CheckWin(board, ROW, COL);
if(ret !='C'){
break;
}
DisplayBoard(board, ROW, COL);
}
if(ret =='*'){
printf("恭喜您贏了!\n");
}else if(ret =='#'){
printf("遺憾計算機贏了!\n");
}else if(ret == 'Q'){
printf("平手!\n");
}
DisplayBoard(board, ROW, COL);
}
3、以上提及函式的實現:
(1)InitBoard函式在每次game()函式開始初始化棋盤:將二維陣列board中的元素全部清空為空格
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] = ' ';//遍歷二維陣列後清空
}
}
}
(2)DisplayBoard函式 視覺化棋盤:
我們希望實現的棋盤如下圖:
為保留三子棋的遊戲推廣性,我們如下編寫程式碼:
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 < col;j++){
printf("---");
if(j<col-1){
printf("|");
}
}
printf("\n");
}
}
}
(3)PlayerMove函式即玩家輸入座標實現下棋操作:
void PlayerMove(char board[ROW][COL], int row, int col){
printf("玩家開始:>\n");
while(1){
int x, y;
scanf("%d %d", &x, &y);
if(x<=row && x>=1 && y<=col && y>=1){
if(board[x-1][y-1] ==' '){
board[x - 1][y - 1] = '*';
break;//核心不可丟
}else{
printf("當期棋盤已被佔有,請重新輸入!\n");//防止重複輸入
}
}else{
printf("輸入不在棋盤當中!\n");//控制合理合理輸入
}
}
}
(4)ComputerMove函式電腦隨機生成座標下棋
void ComputerMove(char board[ROW][COL], int row, int col){
printf("電腦開始:>\n");
while(1)
{
int x = rand() % row;//利用rand生成行
int y = rand() % col;//利用rand生成列
if(board[x][y] == ' '){
board[x][y] = '#';
break;
}
}
}
(5)CheckWin函式,無論玩家或是電腦走棋後,判斷當前輸贏:
三子棋的規則要求同一行同一列,或是對角線達到3顆同樣棋子都可以獲勝於是我們便有了判斷獲勝的條件。
但是平局的問題如何解決?我們需要另一個函式IsFull 來判斷是否9個格子全部下滿了,去判斷是否達到平局。
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 CheckWin(char board[ROW][COL], int row, int col){
int i = 0;
for (i = 0; i < row;i++){
if(board[i][0]==board[i][1] && board[i][1]==board[i][2] && board[i][0] != ' '){
return board[i][0];//判斷行,返回值恰好可以代表玩家或電腦,(#為電腦,*為玩家),下同
}
}
for (i = 0; i < col;i++){
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];//判斷列
}
}
if(board[0][0]==board[1][1] && board[1][1] == board[2][2] && board[0][0]!= ' '){
return board[0][0];//判斷對角線1
}
if(board[0][2]==board[1][1] && board[1][1]==board[2][0] && board[0][2] !=' '){
return board[0][2];//判斷對角線2
}
if(IsFull(board,row,col)==1){
return 'Q';//平局傳遞符號
}
return 'C';//聯絡上文的主函式
}
至此三子棋遊戲已經實現,下面貼上全部程式碼:
#include <stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 3
#define COL 3
void menu();
void game();
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);
int AI(char board[ROW][COL], int row, int col);
char CheckWin(char board[ROW][COL], int row, int col);
int IsFull(char board[ROW][COL], int row, int col);
int main(){
srand((unsigned int)time(NULL));
int input = 0;
do{
menu();
printf("請選擇:>\n");
scanf("%d", &input);
switch(input){
case 1:
printf("遊戲開始\n");
game();
break;
case 0:
printf("退出遊戲!\n");
break;
default:
printf("輸入錯誤,請重新輸入\n");
break;
}
} while (input);
return 0;
}
void menu(){
printf("****************\n");
printf("*****1.play*****\n");
printf("*****0.exit*****\n");
printf("****************\n");
}
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 < 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 < col;j++){
printf("---");
if(j<col-1){
printf("|");
}
}
printf("\n");
}
}
}
void PlayerMove(char board[ROW][COL], int row, int col){
printf("玩家開始:>\n");
while(1){
int x, y;
scanf("%d %d", &x, &y);
if(x<=row && x>=1 && y<=col && y>=1){
if(board[x-1][y-1] ==' '){
board[x - 1][y - 1] = '*';
break;
}else{
printf("當期棋盤已被佔有,請重新輸入!\n");
}
}else{
printf("輸入不在棋盤當中!\n");
}
}
}
void ComputerMove(char board[ROW][COL], int row, int col){
printf("電腦開始:>\n");
while(1)
{
int x = rand() % row;
int y = rand() % col;
if(board[x][y] == ' '){
board[x][y] = '#';
break;
}
}
}
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 CheckWin(char board[ROW][COL], int row, int col){
int i = 0;
for (i = 0; i < row;i++){
if(board[i][0]==board[i][1] && board[i][1]==board[i][2] && board[i][0] != ' '){
return board[i][0];
}
}
for (i = 0; i < col;i++){
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];
}
}
if(board[0][0]==board[1][1] && board[1][1] == board[2][2] && board[0][0]!= ' '){
return board[0][0];
}
if(board[0][2]==board[1][1] && board[1][1]==board[2][0] && board[0][2] !=' '){
return board[0][2];
}
if(IsFull(board,row,col)==1){
return 'Q';
}
return 'C';
}
void game(){
char board[ROW][COL];
char ret = 0;
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
while(1){
PlayerMove(board, ROW, COL);
ret = CheckWin(board, ROW, COL);
if(ret !='C'){
break;
}
DisplayBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
ret = CheckWin(board, ROW, COL);
if(ret !='C'){
break;
}
DisplayBoard(board, ROW, COL);
}
if(ret =='*'){
printf("恭喜您贏了!\n");
}else if(ret =='#'){
printf("遺憾計算機贏了!\n");
}else if(ret == 'Q'){
printf("平手!\n");
}
DisplayBoard(board, ROW, COL);
}
Part 2 AI實現的設想:
我們發現電腦隨機生成,會造成玩家贏得太輕鬆;AI實現其實就是人腦思維在計算機上的實現,我們來思考一下,我們是怎麼下三子棋的,進攻策略上當在行、列、對角線上如果出現了兩個我方棋子,我們會繼續下第三個;防守策略上,敵方在行、列、對角線上有了兩個棋子,我們會堵截;進攻、防守策略上相同的點便出現了電腦需要的就是在出現兩個相同的棋子的地方下自己的棋子就好了(不管是己方還是敵方):
於是程式碼如下:
int AI(char board[ROW][COL], int row, int col){
int i = 0, j = 0;
for (i = 0; i < row;i++){
if(board[i][0]==board[i][1] && board[i][0]!=' '&& board[i][2]==' ')//此處需要注意判斷堵截的棋子是否未下過
{
board[i][2] = '#';
return 1;//返回值為判斷AI執行
}else if(board[i][1]==board[i][2] && board[i][1]!=' '&& board[i][0]==' '){
board[i][0] = '#';
return 1;
}else if(board[i][2]==board[i][0] && board[i][0]!=' '&& board[i][1]==' '){
board[i][1] = '#';
return 1;
}
}
for (i = 0; i < col;i++){
if(board[0][i]==board[1][i] && board[1][i]!=' ' && board[2][i]==' '){
board[2][i] = '#';
return 1;
}
if(board[1][i]==board[2][i] && board[1][i]!=' '&& board[0][i]==' '){
board[0][i] = '#';
return 1;
}
if(board[0][i]==board[2][i] && board[0][i]!=' '&& board[1][i]==' '){
board[1][i] = '#';
return 1;
}
}
if(board[0][0]==board[1][1] && board[1][1]!=' '&& board[2][2]==' '){
board[2][2] = '#';
return 1;
}
if(board[2][2]==board[1][1] && board[1][1]!=' '&& board[0][0]==' '){
board[0][0] = '#';
return 1;
}
if(board[0][0]==board[2][2] && board[0][0]!=' '&& board[1][1]==' '){
board[1][1] = '#';
return 1;
}
if(board[0][2]==board[1][1] && board[1][1]!=' '&& board[2][0]==' '){
board[2][0] = '#';
return 1;
}
if(board[2][0]==board[1][1] && board[1][1]!=' '&& board[0][2]==' '){
board[0][2] = '#';
return 1;
}
if(board[2][0]==board[0][2] && board[2][0]!=' '&& board[1][1]==' '){
board[1][1] = '#';
return 1;
}
return 0;//返回值為判斷AI未執行
}
我們還需要修改一下啊ComputerMove函式
void ComputerMove(char board[ROW][COL], int row, int col){
printf("電腦開始:>\n");
while(1)
{
if(AI(board,row,col)==1){
break;
}//此處修改,若不需要執行AI,則正常的隨機生成座標
int x = rand() % row;
int y = rand() % col;
if(board[x][y] == ' '){
board[x][y] = '#';
break;
}
}
}
總結:
AI函式的程式碼依賴於三子棋的可供選擇空間較小,當然思路模仿了人下棋的正常思維。
Part 3 五子棋的推廣:
三子棋畢竟是小遊戲,我們希望實現在很大的棋盤上,下五子棋,觀察先前的函式,唯一需要修改的是CheckWin函式,和巨集定義裡的ROW和COL(這個只控制棋盤的大小)
char CheckWin(char board[ROW][COL], int row, int col){
int i = 0,j=0;
for (i = 0; i < row;i++){
for (j = 0; j < row - 4;j++){
if (board[i][j] == board[i][j+1] && board[i][j+1] == board[i][j+2] && board[i][j+2] == board[i][j+3] && board[i][j+3] == board[i][j+4] && board[i][j] != ' ')
{
return board[i][j];//此處尋找每一行的是否獲勝
}
}
}
for (i = 0; i < col;i++){
for (j = 0; j < row - 4;j++){
if(board[j][i]==board[j+1][i]&&board[j+2][i]==board[j+1][i]&&board[j+2][i]==board[j+3][i]&&board[j+4][i]==board[j+3][i]&&board[j][i]!=' '){
return board[j][i];//此處此處利用迴圈尋找每一列的是否獲勝
}
}
}
for (j = 0; j < row - 4;j++){
for (i = 0; i < row - 4; i++)
{
if (board[i+j][i] == board[i + j + 1][i + 1] && board[i + j + 1][i + 1] == board[i +j+ 2][i + 2] && board[i + j+ 2][i + 2] == board[i +j+ 3][i + 3] && board[i +j+ 3][i + 3] == board[i +j+ 4][i + 4] && board[i+j][i] != ' ')
{
return board[i+j][i];//我們發現斜線獲勝逐漸變得複雜,需要兩層迴圈來控制;此時我們需要思考如何平移斜線(右平移和下平移)
}
if (board[i][i+j] == board[i + 1][i + j + 1] && board[i + 1][i + j + 1] == board[i + 2][i +j+ 2] && board[i + 2][i + j+ 2]== board[i + 3][i +j+ 3] && board[i + 3][i +j+ 3] == board[i + 4][i +j+ 4] && board[i][i+j] != ' ')
{
return board[i][i+j];
}
}
}
for (j = 0; j < row - 4;j++){
for (i = 0; i < row - 4; i++)
{
if (board[i][row - i - 1-j] == board[i + 1][row - i - 2-j] && board[i + 1][row - i - 2-j] == board[i + 2][row - i - 3-j] && board[i + 2][row - i - 3-j] == board[i + 3][row - i - 4-j] && board[i + 3][row - i - 4-j] == board[i + 4][row - i - 5-j] && board[i][row - i - 1-j] != ' ')
{
return board[i][row - i - 1-j];//同上思路
}
if (board[i+j][row - i - 1-j] == board[i + 1+j][row - i - 2-j] && board[i + 1+j][row - i - 2-j] == board[i + 2+j][row - i - 3-j] && board[i + j+2][row - i - 3-j] == board[i +j+ 3][row - i - 4-j] && board[i +j+ 3][row - i - 4-j] == board[i + j+4][row - i - 5-j] && board[i+j][row - i - 1-j] != ' ')
{
return board[i+j][row - i - 1-j];
}
}
}
if (IsFull(board, row, col) == 1)
{
return 'Q';
}
return 'C';
}
有了這種方法棋盤可以無限擴大,從而使得五子棋變得更有意思。
當然此時可以再利用Part 2的思路使得計算機變得更聰明,但這種演算法在無限擴大的棋盤上,顯得過於臃腫,但限於作者水平,並沒有演示出來
Part 4 總結:
三子棋遊戲是c語言利用陣列這一模組知識可以寫出的較為基礎的小遊戲,但是其背後似乎也蘊含著類似Alpha Go的AI演算法思想,作者也在不斷編寫的過程中,收穫了更加廣闊的思路。同時三子棋不是侷限,類似初高中學習,我們可以通過改進來實現從特殊到一般的過程。