前言:大家应该都了解三子棋这个简单的小游戏吧,那本文就就介绍了如何用C语言实现这个简单的小游戏,就本人而言,这个是我接触的第二个较大的程序,也是现如今最大的一个,其中有一些内容在我前面有一篇文章详解猜数字小游戏https://mp.csdn.net/mp_blog/creation/editor/127400224中涉及到,大家可以先解决那个小游戏,这样可以更好的入门三子棋。同时,这篇文章不仅仅只有三子棋代码,还有我对其的一些小小优化,大家有兴趣的可以阅读文章,也希望我的文章可以给各位带来一些收获。
一、游戏的简单介绍
三子棋游戏的规则和原理我就不再详细介绍了,我在此就说明一下该游戏的代码实现需要透彻地了解循环结构、函数、数组等相关内容,并且需要我们知道一些基本的分文件写函数的一些知识,希望大家可以好好理解一下,以能够更好地实现这个小程序。
二、游戏的大致框架
该游戏开始的大致框架与猜数字小游戏的差不多,这里就不再详细解释了,大家可以访问我的那篇文章去好好了解一下,这里就直接把相关代码给到各位。
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
printf("****************\n");
printf("*****1.play*****\n");
printf("*****o.exit*****\n");
printf("****************\n");
}
void game()
{
}
int main()
{
menu();
int a = 0;
do
{
printf("请选择:>");
scanf("%d", &a);
switch (a)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入失败,请重新输入\n");
break;
}
} while (a);
return 0;
}
但是,框架不仅仅于此,我们还知道当程序较为复杂的时候,我们可以分文件写不同的模块。例如,在三子棋中,我建立的三个文件,两个源文件(游戏主体.c与game.c),一个头文件(game.h)。其中源文件游戏主体.c中写的是这个游戏的基本框架,头文件game.h中是对自定义函数的定义,而源文件game.c也是我们这个程序最重要的部分,其中包含了我们对自定义函数作用的实现。
三、游戏的主要内容(重要)
基本框架确立后,由此也可以知道我们要在定义的game函数中实现游戏的主要内容,而游戏的主要内容就体现在下面六个方面。
由我们对棋盘的基本了解可以知道棋盘是一个3*3的正方形,而此时就能很好地运用一个二维数组来建立这个棋盘,所以为了建立棋盘,我们要先定义一个二维数组,而为了能够使棋盘产生动态变化,我们在头文件中定义棋盘的行与列,这样就将棋盘建立起来了。
为了能够更好地进行后面的步骤,首先我们要将建立的棋盘初始化,而为了不影响其他内容,我们可以将棋盘的所有元素初始化为空格(这也是我们用char来定义数组的一大原因)。此时我们就可以编写我们的第一个函数Initboard(board,ROW,COL)了,它的作用就是将数组(棋盘)内的元素全部初始化为空格,相关代码见下
在game.c中实现:
然后我们再在game函数中调用它,这样棋盘初始化的步骤就完成了。
棋盘的显示就是将棋盘的内容打印在屏幕上,因此我们可以定义一个在屏幕上打印棋盘的函数print_board(board,ROW,COL);而这个函数的实现在game.c中进行,主要与你要打印的棋盘的形状息息相关,我选择打印的形状是
分层具体分析:
分析该形状可以找到一些相似点,将它分成五行再分析我们可以找到一些规律:
(1) 纵向分析得: 1、3、5行的形状一样,共出现了三次;2、4行的形状一样,共出现了两次;
这样在行数的循环中打印( | | )形状就会出现三次,而打印(---|---|---)形状就只会出现两次
(2) 横向分析得:大空格与---都出现了三次,而各行的 | 只出现了两次;所以在各行的打印中,列
数的循环又会有所不同,打印( )要进行三次,而打印( | )只用进行两次。
分析(1)(2)大概可知各行中的( | )总是少一个(即不设置边界)
由这两点规律就可以开始着手写代码实现自己的思路
void print_board(char board[ROW][COL], int row, int col)
{
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)//1、3、5行内容的打印
{
printf(" %c ", board[i][j]);//( )打印col次
if (j < col - 1)
{
printf("|");//1、3、5行的(|)打印col-1次
}
}
printf("\n");//别忘了换行哟
if(i < row - 1)
{
for (j = 0;j < col;j++)//2、4行内容的打印
{
printf("---");//打印col次
if (j < col - 1)
{
printf("|");//打印col-1次
}
}
}
printf("\n");
}
}
注意:这里再说明一个重点,那就是代码
我们要好好思考一下为什么这里是( %c ),这个%c代表着棋盘数组中的元素,此处这样使用是为了更好的执行下棋过程,而左右两边的两个空格则是为了与下面的(---)对齐。
这样,再在头文件中定义一下,在game函数中调用一下,棋盘显示这一步骤就完成了。
设置完打印棋盘的函数后,我们就要正式开始下棋了,这里就默认玩家先落子。而玩家下棋我们又可以定义一个函数PlayerM(board,ROW,COL)来实施,接下来就介绍一下这个函数如何执行。
首先我们要知道,玩家可能会输入错误的坐标或选择的坐标已有棋子,所以我们要在玩家输入有误时给到相应的提示,并且我们由此可知玩家有多次下棋的机会,直至落子成功。因此我们要将这个完整的过程放在一个循环体中进行,玩家落子成功就跳出循环(利用break即可)。
下面直接展示相关代码,比较简单,自己可以好好理解一下。
void PlayerM(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 0 && x <= row && y >= 0 && y <= col)//坐标合法性
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("坐标被占用,请重新输入\n");
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
}
注意:输入的坐标与要访问坐标对应的数组下标是不一样的,所以就有(x-1)与(y-1)两个过程。
为了有显示效果,我们可以在调用完这个函数后再调用一次print_board(board,ROW,COL)函数,这样我们在执行过程中就可以很好的看到函数的实施效果了。还有一点就是,别忘了在头文件中定义这个函数哟。
头文件中定义:
game函数中调用:
这样玩家下棋的步骤也就结束了,接下来就是电脑下棋了。
知道了玩家怎么落子后,其实编写电脑下棋的代码也就简单了。这里主要讲一下二者的区别:
(1)电脑下棋不需要多次提示,所以提示放在循环体外部就行了
(2)我自主设置了玩家下(*),电脑下(#)
(3)电脑下棋可以规定下棋的范围,所以不会出现坐标非法的情况,那我们要使电脑随机落子就又要用到中产生随机的过程了,这个我在猜数字游戏中详细讲解了,在这就不再赘述了,大家可以先去看一看。
了解这三点,下面的函数代码就不难编写了,各位自己要好好理解一下。
void ComputM(char board[ROW][COL], int row, int col)
{
srand(time(NULL));
printf("电脑下棋\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
执行效果:
头文件中定义:
game函数中调用:(再在之后调用一个print_board函数看电脑落子的效果)
这样电脑下棋的步骤就结束了,接下来我们就要进行这个小游戏的高潮部分了,那就是如何判断输赢。
游戏胜负的判断也可以用一个函数执行,那我就将这个函数命名为Is_Win(board,ROW,COL);我们的首要任务不是直接着手去编写这个函数,我们要先思考下列几个问题:
(1) 该函数在game函数中如何调用才能达到最好的效果
(2) 判断胜负的三种情况
(3) 该函数的执行思路是怎样的
解答:
(1) 就问题(1)来说,我们首先要知道,这个对弈的过程是一个持续的过程,没有出结果是不会结束的,所以我们就要把玩家与电脑对弈的函数放在循环体内,当满足结果出现的条件时就离开循环体(即Is_Win(board,ROW,COL)函数判断结果出现)。
(2) 问题(2)应该不难吧,只要下过棋的人就知道下棋结果有三种,即胜、负、和棋(平局)
(3) 问题(3)就是我们需要思考的重点了,它决定了函数执行的方式。
因为我们设置的是玩家以 * 落子,电脑以 # 落子,为了方便判断,我们可以设置以下的方案来更好的实现函数:
【1】 如果玩家获胜,Is_Win(board,ROW,COL)函数就返回 ‘*’
【2】 如果电脑获胜,Is_Win(board,ROW,COL)函数就返回 ‘#’
【3】 如果和棋,Is_Win(board,ROW,COL)函数就返回 ‘D’,而这里的是否和棋又要好好深究一下。 和棋的条件即:棋盘下满了,但是还没有胜者。此时我们又要在Is_Win(board,ROW,COL)函数中再调用一个自定义函数Is_Full(board,ROW,COL),它的作用就是在判断胜负时判断是否和棋以及维持游戏继续进行。
【4】 如果上述三种都不发生,则游戏继续,Is_Win(board,ROW,COL)函数就返回‘C’,表示游戏继续
最后要思考的就是获胜的条件,这个也是个简单的问题,三子棋获胜无非就是行或列或对角线有三个相同的棋子,这里就直接展示相关代码了。
game.c文件中实现该函数:
int Is_Full(char board[ROW][COL], int row, int col)//判断棋盘是否下满(即和棋)
{
int i = 0;
int j = 0;
int count = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char Is_Win(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)//每行是否连棋的判断
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{
return board[i][1];//直接返回相同的棋子,简化了步骤
}
}
for (j = 0;j < col;j++)//每列是否连棋的判断
{
if (board[0][j] == board[1][j] && board[2][j] == board[1][j] && board[1][j] != ' ')
{
return board[1][i];
}
}
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[2][0] == board[1][1] && board[1][1] != ' ')
{
return board[1][1];//右对角线的判断
}
int ret = Is_Full(board, row, col);//判断是否和棋,即棋盘是否被下满
if (ret == 0)
return 'C';//没下满
else
return 'D';//下满了
}
游戏主体.c文件中game函数的完整调用:
void game()
{
char board[ROW][COL];//定义二维数组建立棋盘
char ret = 0;
Initboard(board, ROW, COL);//初始化棋盘
print_board(board, ROW, COL);//打印棋盘
while (1)//游戏未结束时达到多次下棋的效果
{
PlayerM(board, ROW, COL);//玩家下棋
print_board(board, ROW, COL);
ret = Is_Win(board, ROW, COL);//判断胜负
if (ret != 'C')
break;
ComputM(board, ROW, COL);//电脑下棋
print_board(board, ROW, COL);
ret = Is_Win(board, ROW, COL);//再次判断胜负
if (ret != 'C')
break;
}
if (ret == '*')
printf("玩家获胜\n");
else if (ret == '#')
printf("电脑获胜\n");
else if(ret == 'D')
printf("和棋\n");
print_board(board, ROW, COL);
}
game.h文件中的完整定义:
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 3
#define COL 3
//在头文件中定义函数,使得调用函数时引用该头文件即可
void Initboard(char board[ROW][COL], int row, int col);//初始化棋盘
void print_board(char board[ROW][COL], int row, int col);//打印棋盘
void PlayerM(char board[ROW][COL], int row, int col);//玩家落子
void ComputM(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);//判断棋盘是否下满(即和棋)
就这样,一个简易的三子棋游戏就可以实现了。
注意:这里的判断条件(Is_Win()函数)太局限了,只满足三子棋的实现,所以该代码还需优化,具体见下。
四、游戏的相关优化
以我当前的水平,仅仅只能够对Is_Win()函数进行优化,优化后可以实现N*N棋盘N子棋的进行。优化后的代码见下:
char Is_Win(char board[ROW][COL], int row, int col)//利用*的ASCII码值为42,#的ASCII码值为35
{
int i = 0;
int j = 0;
int sum = 0;
for (i = 0;i < row;i++)//逐行判断
{
sum = 0;//这个要放在循环体内部,以达到每次都清零的效果
for (j = 0;j < col;j++)
{
sum += board[i][j];
}
if (sum == col * 42)
return '*';
else if (sum == col * 35)
return '#';
}
for (i = 0;i < col;i++)//逐列判断
{
sum = 0;
for (j = 0;j < row;j++)
{
sum += board[j][i];
}
if (sum == row * 42)
return '*';
else if (sum == row * 35)
return '#';
}
sum = 0;
for (i = 0;i < row;i++)//左对角线
{
sum += board[i][i];
}
if (sum == row * 42)
return '*';
else if (sum == row * 35)
return '#';
sum = 0;
for (i = 0, j = col - 1;i < row;i++, j--)//右对角线
{
sum += board[i][j];
}
if (sum == row * 42)
return '*';
else if (sum == row * 35)
return '#';
int ret = Is_Full(board, row, col);//和棋判断
if (ret == 0)
return 'C';
else
return 'D';
}
再通过改变ROW与COL的值就可以实现优化了,这里以5*5棋盘五子棋为例展示一下:
大家理解后就可以自己实现一下这个小游戏,好好玩一玩。
五、 游戏完整代码
1. 头文件game.h
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 5
#define COL 5
//在头文件中定义函数,使得调用函数时引用该头文件即可
void Initboard(char board[ROW][COL], int row, int col);//初始化棋盘
void print_board(char board[ROW][COL], int row, int col);//打印棋盘
void PlayerM(char board[ROW][COL], int row, int col);//玩家落子
void ComputM(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);//判断棋盘是否下满(即和棋)
2.源文件游戏主体.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
printf("****************\n");
printf("*****1.play*****\n");
printf("*****o.exit*****\n");
printf("****************\n");
}
void game()
{
char board[ROW][COL];//定义二维数组建立棋盘
char ret = 0;
Initboard(board, ROW, COL);//初始化棋盘
print_board(board, ROW, COL);//打印棋盘
while (1)//游戏未结束时达到多次下棋的效果
{
PlayerM(board, ROW, COL);//玩家下棋
print_board(board, ROW, COL);
ret = Is_Win(board, ROW, COL);//判断胜负
if (ret != 'C')
break;
ComputM(board, ROW, COL);//电脑下棋
print_board(board, ROW, COL);
ret = Is_Win(board, ROW, COL);//再次判断胜负
if (ret != 'C')
break;
}
if (ret == '*')
printf("玩家获胜\n");
else if (ret == '#')
printf("电脑获胜\n");
else if(ret == 'D')
printf("和棋\n");
print_board(board, ROW, COL);
}
int main()
{
menu();
int a = 0;
do
{
printf("请选择:>");
scanf("%d", &a);
switch (a)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入失败,请重新输入\n");
break;
}
} while (a);
return 0;
}
3. 源文件game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void Initboard(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
board[i][j] = ' ';
}
}
}
void print_board(char board[ROW][COL], int row, int col)
{
int i = 0;
int 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 PlayerM(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 0 && x <= row && y >= 0 && y <= col)//坐标合法性
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("坐标被占用,请重新输入\n");
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
}
void ComputM(char board[ROW][COL], int row, int col)
{
srand((unsigned)time(NULL));
printf("电脑下棋\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
int Is_Full(char board[ROW][COL], int row, int col)//判断棋盘是否下满(即和棋)
{
int i = 0;
int j = 0;
int count = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
/*char Is_Win(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)//每行是否连棋的判断
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{
return board[i][1];//直接返回相同的棋子,简化了步骤
}
}
for (j = 0;j < col;j++)//每列是否连棋的判断
{
if (board[0][j] == board[1][j] && board[2][j] == board[1][j] && board[1][j] != ' ')
{
return board[1][i];
}
}
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[2][0] == board[1][1] && board[1][1] != ' ')
{
return board[1][1];//右对角线的判断
}
int ret = Is_Full(board, row, col);//判断是否和棋,即棋盘是否被下满
if (ret == 0)
return 'C';//没下满
else
return 'D';//下满了
}*/
char Is_Win(char board[ROW][COL], int row, int col)//利用*的ASCII码值为42,#的ASCII码值为35
{
int i = 0;
int j = 0;
int sum = 0;
for (i = 0;i < row;i++)//逐行判断
{
sum = 0;//这个要放在循环体内部,以达到每次都清零的效果
for (j = 0;j < col;j++)
{
sum += board[i][j];
}
if (sum == col * 42)
return '*';
else if (sum == col * 35)
return '#';
}
for (i = 0;i < col;i++)//逐列判断
{
sum = 0;
for (j = 0;j < row;j++)
{
sum += board[j][i];
}
if (sum == row * 42)
return '*';
else if (sum == row * 35)
return '#';
}
sum = 0;
for (i = 0;i < row;i++)//左对角线
{
sum += board[i][i];
}
if (sum == row * 42)
return '*';
else if (sum == row * 35)
return '#';
sum = 0;
for (i = 0, j = col - 1;i < row;i++, j--)//右对角线
{
sum += board[i][j];
}
if (sum == row * 42)
return '*';
else if (sum == row * 35)
return '#';
int ret = Is_Full(board, row, col);//和棋判断
if (ret == 0)
return 'C';
else
return 'D';
}
总结
这是我至今写博客写的最长的一篇,也是耗时最多的一篇,感悟也颇多,希望能给对三子棋有兴趣的朋友一些帮助,这次的分享就到这里结束了,再见了各位!!!