文章目录
前言
三子棋的实现其实并不难,只要大家理解游戏是如何进行的就可以通过简单的代码来实现。本文会教大家如何使用C语言实现最简单的三子棋游戏,并且会说一点让电脑变“聪明”的方法。
一、设计思路
1.设计一个游戏应该有个开始的选择菜单;
2.三子棋是个3×3的棋盘,创建一个二维数组作为棋盘;
3.棋盘初始为空的,先初始化棋盘;
4.打印出棋盘给玩家看,应打印出如图有线条的棋盘;
5.实现玩家下棋;
6.实现电脑下棋;
7.设计个判断输赢的函数,每次下棋后都进行一次判断。
二、游戏实现
我们创建三个文件来进行编程,test.c,game.c,game.h,test.c用来放main函数进行游戏的测试运行,game.h和game.c分别放游戏的声明和定义。
1.菜单
game.c中定义:
//打印菜单
void meun()
{
printf("**********************\n");
printf("** 1.play ***\n");
printf("** 0.exit ***\n");
printf("**********************\n");
}
我们定义菜单有两个选项,输入“1”进入游戏函数,输入“0”退出游戏,其他则“输入错误”。我们考虑玩家玩完一局还想再玩的话菜单就要重复打印,那么菜单就应该放入循环里,而菜单必先打印一次,所以我们使用do while循环。
test.c中实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//三子棋游戏
void game()
{
printf("游戏开始\n");
}
int main()
{
int input = 0;
do
{
meun();
printf("请选择:>");
scanf("%d", &input);
if (1 == input)
{
game();
}
else if (0 == input)
{
printf("退出游戏\n");
}
else
printf("输入错误,请重新输入\n");
} while (input);
return 0;
}
测试:
2.初始化棋盘
我们在头文件game.h中定义行ROW为3,列COL为3。这样子行和列就可以轻易更换。
#define ROW 3
#define COL 3
game.c中定义:
我们定义创建的二维数组棋盘初始都为空格。
//初始化棋盘
void init_board(char board[ROW][COL])
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
board[i][j] = ' ';
}
}
}
3.打印棋盘
但是我们发现循环打印的最后一次会少个“ | ”,所以我们在打印“ | ”前可以先做个判断来解决。
game.c中定义:
//打印棋盘
void print(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
printf("\n");
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");
}
}
printf("\n");
}
test.c中测试:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//三子棋游戏
void game()
{
char board[ROW][COL] = { '0' };
printf("游戏开始:\n");
//初始化棋盘
init_board(board);
//打印棋盘
print(board,ROW,COL);
}
int main()
{
int input = 0;
do
{
meun();
printf("请选择:>");
scanf("%d", &input);
if (1 == input)
{
game();
}
else if (0 == input)
{
printf("退出游戏\n");
}
else
printf("输入错误,请重新输入\n");
} while (input);
return 0;
}
4.玩家下棋
设计玩家下棋时要注意的是玩家要下的坐标有三种情况:
1.该坐标已有棋子;
2.该坐标非法(不在棋盘里);
3.改坐标为“ ”,可以下棋。
所以我们要分别判断三种情况,只有第三种才允许玩家下棋(我定义玩家棋子为“ * ”),否则将重新进入循环让玩家再输入一遍。
小tip:玩家看到棋盘会下意识觉得棋盘的行列是从1开始的,但其实数组的下标是从0开始访问的,所以我们在获取玩家输入的数字时要进行减1的操作才能对应上。
game.c中定义:
//玩家下棋
void player(char board[ROW][COL])
{
int x = 0;
int y = 0;
printf("请输入要下的坐标:>");
while (1)
{
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("已有棋子,请重新输入:>");
}
else
printf("坐标非法,请重新输入:>");
}
}
test.c中测试:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//三子棋游戏
void game()
{
char board[ROW][COL] = { '0' };
printf("游戏开始:\n");
//初始化棋盘
init_board(board);
//打印棋盘
print(board,ROW,COL);
while (1)
{
printf("玩家下棋:\n");
//玩家下棋
player(board);
print(board, ROW, COL);
}
}
int main()
{
int input = 0;
do
{
meun();
printf("请选择:>");
scanf("%d", &input);
if (1 == input)
{
game();
}
else if (0 == input)
{
printf("退出游戏\n");
}
else
printf("输入错误,请重新输入\n");
} while (input);
return 0;
}
5.电脑下棋
我们制作最简单的三字棋时考虑电脑是进行随机下棋的,所以我们用上rand()函数来生成随机数(使用rand函数生成随机数要在主函数先使用srand函数),%行或者列就可以得到0~2的数。只要棋盘中为“ ”就下棋。(我定义电脑棋子为“#”)
game.c中定义:
//电脑下棋
void computer(char board[ROW][COL])
{
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
test.c中测试
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
//三子棋游戏
void game()
{
char board[ROW][COL] = { '0' };
printf("游戏开始:\n");
//初始化棋盘
init_board(board);
//打印棋盘
print(board,ROW,COL);
while (1)
{
printf("玩家下棋:\n");
//玩家下棋
player(board);
printf("电脑下棋:\n");
//电脑下棋
computer(board);
print(board, ROW, COL);
}
}
//三子棋游戏
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
meun();
printf("请选择:>");
scanf("%d", &input);
if (1 == input)
{
game();
}
else if (0 == input)
{
printf("退出游戏\n");
}
else
printf("输入错误,请重新输入\n");
} while (input);
return 0;
}
6.判断输赢
我们要先知道判断时有几种情况:
1.玩家三子连在一起获胜:对角线,横,竖;
2.电脑三子连在一起获胜;
3.平局;
4.未分胜负,游戏继续;
我们不妨设judge函数结束时会返回一个字符类型:
1.玩家获胜返回“*”;
2.电脑获胜返回“#”;
3.平局返回“D”;
4.游戏继续返回“C”。
小tip:我们用循环的方式一个个访问数组里的元素,判断是第几种情况。我们发现玩家获胜返回的字符和玩家下的棋子都是“*”,所以可以直接return数组中的元素以此简化代码。(电脑获胜同上)
game.c中定义:
//判断输赢
char judge(char board[ROW][COL])
{
int i = 0;
int j = 0;
int flag = 1;
//斜对角判断
for (i = 0, j = 0; i < ROW-1, j < COL-1; i++, j++)
{
if (board[i][j] != board[i + 1][j + 1] || board[i][j] == ' ')
{
break;
}
if (i == ROW - 2)
return board[i][j];
}
for (i = 0, j = ROW-1; i < ROW - 1, j >0; i++, j--)
{
if (board[i][j] != board[i + 1][j - 1] || board[i][j] == ' ')
{
break;
}
if (i == ROW - 2)
return board[i][j];
}
//行判断
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL-1; j++)
{
if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
break;
if (j == COL - 2)
return board[i][j];
}
}
//列判断
for (j = 0; j < COL; j++)
{
for (i = 0; i < ROW - 1; i++)
{
if (board[i][j] != board[i + 1][j] || board[i][j] == ' ')
break;
if (i == ROW - 2)
return board[i][j];
}
}
//平局判断
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
{
flag = 0;
break;
}
}
}
if (flag == 1)
return 'D';
else
return 'C';
}
test.c中测试:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
//三子棋游戏
void game()
{
char jud = 0;
char board[ROW][COL] = { '0' };
printf("游戏开始:\n");
//初始化棋盘
init_board(board);
//打印棋盘
print(board,ROW,COL);
while (1)
{
printf("玩家下棋:\n");
player(board);
//print(board, ROW, COL);
//判断输赢
jud = judge(board);
if (jud != 'C')
{
break;
}
printf("电脑下棋:\n");
computer(board);
jud = judge(board);
if (jud != 'C')
{
break;
}
print(board, ROW, COL);
}
if (jud == '*')
{
print(board, ROW, COL);
printf("恭喜你,获胜了\n");
}
if (jud == '#')
{
print(board, ROW, COL);
printf("你输了,电脑获胜\n");
}
if (jud == 'D')
{
print(board, ROW, COL);
printf("游戏结束,平局\n");
}
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
meun();
printf("请选择:>");
scanf("%d", &input);
if (1 == input)
{
game();
}
else if (0 == input)
{
printf("退出游戏\n");
}
else
printf("输入错误,请重新输入\n");
} while (input);
return 0;
}
三、游戏演示
四、原代码
1.game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3
//打印菜单
void meun();
//初始化棋盘
void init_board(char board[ROW][COL]);
//打印棋盘
void print(char board[ROW][COL], int row, int col);
//玩家下棋
void player(char board[ROW][COL]);
//电脑下棋
void computer(char board[ROW][COL]);
//判断输赢
char judge(char board[ROW][COL]);
2.game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//打印菜单
void meun()
{
printf("**********************\n");
printf("** 1.play ***\n");
printf("** 0.exit ***\n");
printf("**********************\n");
}
//初始化棋盘
void init_board(char board[ROW][COL])
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
board[i][j] = ' ';
}
}
}
//打印棋盘
void print(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
printf("\n");
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");
}
}
printf("\n");
}
//玩家下棋
void player(char board[ROW][COL])
{
int x = 0;
int y = 0;
printf("请输入要下的坐标:>");
while (1)
{
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("已有棋子,请重新输入:>");
}
else
printf("坐标非法,请重新输入:>");
}
}
//电脑下棋
void computer(char board[ROW][COL])
{
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
//判断输赢
char judge(char board[ROW][COL])
{
int i = 0;
int j = 0;
int flag = 1;
//斜对角判断
for (i = 0, j = 0; i < ROW-1, j < COL-1; i++, j++)
{
if (board[i][j] != board[i + 1][j + 1] || board[i][j] == ' ')
{
break;
}
if (i == ROW - 2)
return board[i][j];
}
for (i = 0, j = ROW-1; i < ROW - 1, j >0; i++, j--)
{
if (board[i][j] != board[i + 1][j - 1] || board[i][j] == ' ')
{
break;
}
if (i == ROW - 2)
return board[i][j];
}
//行判断
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL-1; j++)
{
if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
break;
if (j == COL - 2)
return board[i][j];
}
}
//列判断
for (j = 0; j < COL; j++)
{
for (i = 0; i < ROW - 1; i++)
{
if (board[i][j] != board[i + 1][j] || board[i][j] == ' ')
break;
if (i == ROW - 2)
return board[i][j];
}
}
//平局判断
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
{
flag = 0;
break;
}
}
}
if (flag == 1)
return 'D';
else
return 'C';
}
3.test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//三子棋游戏
void game()
{
char jud = 0;
char board[ROW][COL] = { '0' };
printf("游戏开始:\n");
//初始化棋盘
init_board(board);
//打印棋盘
print(board,ROW,COL);
while (1)
{
printf("玩家下棋:\n");
player(board);
//print(board, ROW, COL);
//判断输赢
jud = judge(board);
if (jud != 'C')
{
break;
}
printf("电脑下棋:\n");
computer(board);
jud = judge(board);
if (jud != 'C')
{
break;
}
print(board, ROW, COL);
}
if (jud == '*')
{
print(board, ROW, COL);
printf("恭喜你,获胜了\n");
}
if (jud == '#')
{
print(board, ROW, COL);
printf("你输了,电脑获胜\n");
}
if (jud == 'D')
{
print(board, ROW, COL);
printf("游戏结束,平局\n");
}
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
meun();
printf("请选择:>");
scanf("%d", &input);
if (1 == input)
{
game();
}
else if (0 == input)
{
printf("退出游戏\n");
}
else
printf("输入错误,请重新输入\n");
} while (input);
return 0;
}
拓展:游戏优化
我们很容易发现上面这样编出来的三子棋人机就像人工智障一样,明明玩家就要获胜了也不会阻止一下玩家,或者自己就快要赢了也还是乱下。这是因为电脑始终都是下的随机棋,那么我们就可以通过更改电脑下棋函数让电脑稍微聪明一点点:让电脑输赢判断时发现出现了两个相同的字符且第三个位置为“ ”时,就抢先下棋到“ ”上。
computer函数代码优化:
//电脑下棋
void computer(char board[ROW][COL])
{
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
int i = 0;
int j = 0;
int a = 0;
int b = 0;
int count1 = 0;
int count2 = 0;
//抢中轴
if (board[ROW / 2][COL / 2] == ' ')
{
board[ROW / 2][COL / 2] = '#';
break;
}
//斜对角判断
for (i = 0, j = 0, count1 = 0, count2 = 0; i < ROW, j < COL; i++, j++)
{
if (board[i][j] == '#')
count1++;
else if (board[i][j] == '*')
count2++;
else
{
a = i;
b = j;
}
}
if ((count1 == ROW - 1 || count2 == ROW - 1 )&& (count1 + count2) != ROW)
{
board[a][b] = '#';
goto end;
}
for (i = 0, j = COL-1, count1 = 0, count2 = 0; i < ROW, j >=0; i++, j--)
{
if (board[i][j] == '#')
count1++;
else if (board[i][j] == '*')
count2++;
else
{
a = i;
b = j;
}
}
if ((count1 == ROW - 1 || count2 == ROW - 1) && (count1 + count2) != ROW)
{
board[a][b] = '#';
goto end;
}
//行判断
for (i = 0 ; i < ROW; i++)
{
count1 = 0;
count2 = 0;
for (j = 0; j < COL ; j++)
{
if (board[i][j] == '#')
count1++;
else if (board[i][j] == '*')
count2++;
else
{
a = i;
b = j;
}
}
if ((count1 == ROW - 1 || count2 == ROW - 1) && (count1 + count2) != ROW)
{
board[a][b] = '#';
goto end;
}
}
//列判断
for (j = 0; j < COL; j++)
{
count1 = 0;
count2 = 0;
for (i = 0; i < ROW; i++)
{
if (board[i][j] == '#')
count1++;
else if (board[i][j] == '*')
count2++;
else
{
a = i;
b = j;
}
}
if ((count1 == ROW - 1 || count2 == ROW - 1) && (count1 + count2) != ROW)
{
board[a][b] = '#';
goto end;
}
}
if (board[x][y] == ' ')
{
board[x][y] = '#';
end:
break;
}
}
}
优化后游戏演示:
(虽然它已经变聪明了许多,但还是斗不过我们的计谋哈哈)
总结
游戏的设计其实就是把游戏拆分成一个个板块来进行逐一实现,本文介绍了最基本的三子棋的设计,还有小小的优化方法。但是这个三子棋还有很大很大的优化空间,感兴趣的小伙伴可以自己好好研究一下。探究出更厉害的优化方案欢迎各位大佬留言私信,最后,感谢大家阅读到这~