井字棋,想必我们大家都玩过,那么今天就让我们一起来看一下如何利用c语言和EasyX图形库实现井字棋的制作。

编写前的准备

  • 下载好EasyX图形库,并安装在VS上。
  • 对井字棋规则进行深度了解
  • 对函数,数组,循环知识的复习

整体思路

下棋第一步,我们需要准备棋盘和棋子,我们可以利用图片制作棋盘,同时利用图片制作棋子,由于井字棋是3X3布局所以,这里我们采用二维数组的形式来对棋盘上每个位置进行标记。定义map[3] [3]并将其全部初始化为0,同时为了区分棋子,我们规定用户棋子在数组中的值为1,AI为-1,当棋子落入棋盘后,将对应的数组位置填入对应的数字。考虑到规则,一共有八种情况每一个里面有三个位置,每个位置有横纵坐标,此时我们可以想到通过三维数组惊醒定义Key[8] [3] [2]。在这里我们使用loadimage函数实现地图与棋子的加载。由于我们要多次使用棋子,我们可以将棋子定义为IMAGE类型的变量用来标记地图。当棋子下在对应方格中时,对应的map[i] [j]应该发生改变。当然,前面的-1,1我们可以定义为枚举类型来使用,这样能够使我们的结构更加清晰。

void gameInit() {
//创建游戏窗口并设计大小
initgraph(654,654);//宽度和高度
//构建背景
loadimage(0,"S_game4.png");//地图
loadimage(&imgMan, "Man.png");//棋子
loadimage(&imgAI, "AI.png");//棋子
//对地图进行初始化
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
map[i][j] = 0;
}
}
srand(time(NULL));
}

用户信息的输入

首先我们需要写一个死循环保证游戏的运行,由于用户是从鼠标点击棋盘不同位置进行下棋,故我们通过下列代码实现操作:

MOUSEMSG msg= GetMouseMsg();//定义MOUSEMSG类型的变量,获取用户鼠标信息
//判断鼠标信息类型
if (msg.uMsg == WM_LBUTTONDOWN) {//WM_LBUTTONDOWN 鼠标左键按下
//用户下棋
manGo(&msg);
//判断结果

manGo函数接收到鼠标信息后,进行确定位置和输出棋子:

void manGo(MOUSEMSG*msg) {
int H = msg->y / 218; //行
int L = msg->x /218; //列
//判断用户所选位置是否有棋子
if (map[H][L] == 0) {
map[H][L] = Man;//将该位置标记为用户使用
//设置坐标,将标记呈现在所在行列
int x = L * 218 +10;
int y = H * 218 +10;
//使用putimage画图
putimage(x,y,&imgMan);
}
}

AI信息的输入

AI是要模拟人的思想进行操作具体想法如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bYYHOFiF-1671679880549)(C:/Users/wwwZ1/AppData/Roaming/Typora/typora-user-images/image-20221222105055552.png)]

我们可以定义一个find函数用于发现用户与电脑所下棋子与八种情况之间的重合度。根据井字棋规则可知,中间位置为最佳位置,四个顶角位置次之,故先通过map数组对中间位置进行判断,如果map[1] [1] ==0,则说明用户没有下在中间,如果map[1] [1]==1说明用户下在中间,自己则下在四个顶角的任意位置。当下两个棋子以上时,通过find函数的返回值进行对用户或自己是否能胜利进行判断,如果返回值大于0,说明在八种情况中自己符合某种情况,可以取胜,反之不能取胜。

定义的find函数的原理:例如:find(2)首先对八种情况的每一种进行遍历,判断用户所下位置是否是在一种情况中的两个位置,如果是,返回i,说明用户在下一次能够取胜,反之,不能。AI情况与之类似。

int find(int value) {
for (int i = 0; i < 8; i++) {//step1:对8种制胜的情况进行遍历
int s = 0;//用于记录3个位置的标记情况
for (int j = 0; j < 3; j++) {//step2:对每种情况中的3个可能位置进行遍历
int H = Key[i][j][0];//行坐标
int L = Key[i][j][1];//列坐标
s += map[H][L];
}
if (s == value) {
return i;
}
} return -1;
}

结果的判断

下棋的结局一共有三种情况,分别是用户胜利,AI胜利,平局,这时我们可以用之前定义的find函数进行判断是否胜利。平局的情况,我们可以从棋盘是否被占满来进行判断,首先定义一个count变量进行计数,当棋盘中map[i] [j]==0,count++,最后再使用if条件语句进行判断棋盘是否占满。

注意:棋盘的横纵坐标,枚举,函数的使用,EasyX图形库的应用。

C语言课程设计之井字棋_初始化

代码实现

#define _CRT_SECURE_NO_WARNINGS 1
//编写前的准备
/*
* 安装Easyx图形库
* 了解井字棋规则
* 对数组、循环、函数知识回顾
//作用:
initgraph 创建一个窗口
loadimage 加载图片到指定位置
GetMouseMsg(); 获取用户鼠标信息
*/
#include<stdio.h>
#include<graphics.h>//用于画图,Easyx图形库
#include<time.h>//用于生成随机数
//用于播放后台音乐
#include<windows.h>
#pragma comment(lib,"Winmm.lib")
enum { AI = -1, Man = 1 };//枚举------>标记棋子
int map[3][3];//设计地图
IMAGE imgMan;//定义一个IMAGE类型的变量用于存放用户输入的标志
IMAGE imgAI;//定义一个IMAGE类型的变量用于存放电脑输入的标志
//棋子胜利有8种情况,3个格子,每个格子有x、y两个坐标
/*********************************************************************************************************/
int Key[8][3][2] = {
0,0, 0,1, 0,2,//第一行
1,0, 1,1, 1,2,//第二行
2,0, 2,1, 2,2,//第三行
0,0, 1,0, 2,0,//第一列
0,1, 1,1, 2,1,//第二列
0,2, 1,2, 2,2,//第三列
0,0, 1,1, 2,2,//正对角线
0,2, 1,1, 2,0//反对角线
};
/*********************************************************************************************************/
int find(int value) {
for (int i = 0; i < 8; i++) {//step1:对8种制胜的情况进行遍历
int s = 0;//用于记录3个位置的标记情况
for (int j = 0; j < 3; j++) {//step2:对每种情况中的3个可能位置进行遍历
int H = Key[i][j][0];//行坐标
int L = Key[i][j][1];//列坐标
s += map[H][L];
}
if (s == value) {//判断结果
return i;
}
} return -1;
}
/*********************************************************************************************************/
void gameInit() {
//创建游戏窗口并设计大小
initgraph(654,654);//宽度和高度
//构建背景
loadimage(0,"S_game4.png");
loadimage(&imgMan, "Man.png");
loadimage(&imgAI, "AI.png");
//初始化棋盘
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
map[i][j] = 0;
}
}
srand(time(NULL));
}
/*********************************************************************************************************/
//用户下棋
void manGo(MOUSEMSG*msg) {
int H = msg->y / 218; //行
int L = msg->x /218; //列
//判断用户所选位置是否有棋子
if (map[H][L] == 0) {
map[H][L] = Man;//将该位置标记为用户使用
//设置坐标,将标记呈现在所在行列
int x = L * 218 +10;
int y = H * 218 +10;
//使用putimage画图
putimage(x,y,&imgMan);
}
}
/*********************************************************************************************************/
//AI下棋
void aiGo() {
int step = 0;
int H, L;
/*********************************************************************************************************
* 判断哪些位置已经被选择,并记录总共所走的步数
* 遍历数组map的9个位置,确定哪些位置已经被占用
/*********************************************************************************************************/
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (map[i][j]) {
step++;
}
}
}
/*********************************************************************************************************
* 如果step=0,说明棋盘中没有位置被选中,只需要等待用户选择即可
* 井字棋最佳位置时中心位置,其次是四个顶角位置
/*********************************************************************************************************/
if (step == 1) {
//判断中间位置是否有棋子
if (map[1][1] == 0) {
H = 1;
L = 1;
}//将棋子位置放在中间
else {
H = 0;
L = 2;
}//将棋子位置放在四个顶角之一的位置
}
/*********************************************************************************************************
* 当step=2时,对可能出现的情况进行分析
/**********************************************************************************************************/
else {
int i = find(-2);
//当返回值后,i<0,证明没有找到使得AI一举获胜的情况
if (i < 0) {
i = find(2);//对用户情况的判断
}
if (i >= 0)//说明用户有获胜的可能
{
//在第i条路径上的空白位置落子(每条路径有三个位置)
for (int j = 0; j < 3; j++) {
int H1 = Key[i][j][0];//确定获胜路径中的横坐标
int L1 = Key[i][j][1];//确定获胜路径中的纵坐标
if (map[H1][L1] == 0) {//将棋子放在获胜路径中的空位置,实现阻止用户获胜
H = H1;
L = L1;
break;//已经找到位置,终止循环
}
}
}
/*********************************************************************************************************/
else//用户不能一举获胜的情况,棋子位置随意
{
while (1) {
H = rand() % 3;//产生小于3的随机数,赋给行坐标
L = rand() % 3;//产生小于3的随机数,赋给列坐标
if (map[H][L] == 0) {
break;//棋子填入空位置,终止循环
}
}
}
}
putimage(L * 218 + 15, H* 218 + 15, &imgAI);//将标记放在AI所选位置
map[H][L] = AI;//将-1给当前坐标,表明该位置已经被占用
}
/*********************************************************************************************************/
//定义布尔型函数,检查结果
bool check() {
int i = find(3);
//用户获胜
Sleep(250);//保证每次操作都有间歇期
if (i >= 0)
{
loadimage(0, "WIN2.png");
system("pause");
//对地图进行初始化,实现新游戏
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
map[i][j] = 0;
}
}
loadimage(0, "S_game4.png");//地图重加载
return true;
}
/*********************************************************************************************************/
//AI获胜
i = find(-3);
Sleep(250);//保证每次操作都有间歇期
if(i>=0){
loadimage(0, "Defeat.png");
system("pause");
//对地图进行初始化,实现新游戏
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
map[i][j] = 0;
}
}
loadimage(0, "S_game4.png");//地图重加载
return true;
}
/*********************************************************************************************************/
//平局情况 地图上面没有空余位置
int count = 0;//用于统计map数组中是否含有0
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (map[i][j] == 0) {
count++;
}
}
}
if (count == 0)//count=0说明棋盘中没有空余位置
{
loadimage(0, "Ping.png");
system("pause");
//对地图进行初始化,实现新游戏
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
map[i][j] = 0;
}
}
loadimage(0, "S_game4.png");//地图重加载
return true;
}
return false;
}
/*********************************************************************************************************/
int main(void) {
//初始化
gameInit();//用于构建游戏所需要的条件
//游戏的主循环
//设置游戏背景音乐
mciSendString("open 井字游戏.mp3 alias bkmusic", NULL, 0, NULL);
mciSendString("play bkmusic repeat", NULL, 0, NULL);//循环播放音乐
while (1) {
//开始下棋
MOUSEMSG msg= GetMouseMsg();//定义MOUSEMSG类型的变量,获取用户鼠标信息
//判断鼠标信息类型
if (msg.uMsg == WM_LBUTTONDOWN) {//WM_LBUTTONDOWN 鼠标左键按下
//用户下棋
manGo(&msg);
//判断结果
if (check()) {
continue;//如果胜利,跳过循环
};
//电脑下棋
aiGo();
check();
}
}
system("pause");//设置暂停,使窗口停留
return 0;
}
/*********************************************************************************************************/

效果展示

C语言课程设计之井字棋_井字棋_02

感兴趣的小伙伴还可以为它添加多种音效,开始界面的美化,以及其他功能。井字棋的教学到这里就结束了。



重要的事情说三遍:

​一定要先捋清楚思路再尝试代码实现!!!一定要先捋清楚思路再尝试代码实现!!!一定要先捋清楚思路再尝试代码实现!!!​

C语言课程设计之井字棋_c语言_03