整体思路
完整代码会放到文章末尾
这只是实现三子棋游戏的大致思路。
我采用三个文件来实现这个游戏:
1.存放实现三子棋游戏的所需库函数头文件和函数声明的自定义头文件
2.实现各个函数定义的.c文件
3.有一个主函数mian存在的.c文件(在这个文件里面写三子棋游戏的实现方式)
有主函数的.c文件
菜单函数
void menu()
{
printf("***********三子棋**********\n");
printf("*********1.开始游戏********\n");
printf("*********0.退出游戏********\n"); //我们为什么要把0作为退出游戏的选项呢?详情请看下一内容讲解
}
选择是否游戏函数
void test()
{
int input = 0;
do //这里采用do while结构,因为无论如何都需要执行一遍菜单函数
{
menu();
printf("请选择是否要开始游戏:\n");
scanf("%d",&input);
while ((input != 1) && (input != 0))//这里用来判断玩家是否输入的正确
{
printf("选择错误,请重新输入:\n");
scanf("%d", &input);
}
switch (input) //使用switch语句进行玩家想进行的操作
{
case 1:
game();
break;
case 0:
printf("退出游戏\n"); //选择0退出游戏
break;
}
} while (input); //同时因为input=0,do while语句后条件为假,结束循环⭐
}
game()函数
void game()
{
srand(time(NULL)); //调用srand()函数确保每次电脑下棋的位置是随机的
char arr[ROW][COL] = {0}; //创建一个字符二维数组存放玩家和电脑下棋的数据
game_init(arr, ROW, COL); //初始化数组,交给一个函数完成就可以了
game_print(arr, ROW, COL); //按照一定格式打印数组
char c;
while (1)
{
printf("玩家下棋\n"); //玩家先下棋
game_player(arr, ROW, COL); //玩家下棋的具体实现函数
game_print(arr, ROW, COL); //打印一遍棋盘
c = game_win(arr, ROW, COL); //判断输赢的函数,判断输赢的结果有四种:平局,玩家赢,电脑赢,继续玩
if (c != 'C') //用'C'来表示继续玩,如果game()函数返回值不为‘C’(不继续玩),就要跳出循环
break;
printf("电脑下棋\n"); //电脑下棋
game_computer(arr, ROW, COL); //电脑下棋的具体实现函数
game_print(arr, ROW, COL); //打印一遍棋盘
c = game_win(arr, ROW, COL); //依然要判断是谁赢了
if (c != 'C')
break;
}
if (c == '#') //用'#'表示电脑下棋的符号,用'*'表示玩家下棋的符号
printf("电脑赢\n"); //这里赢的返回值为什么也使用的是他们各自的符号呢?⭐
else if (c == '*') //具体为什么,请看 判断输赢的函数 部分的讲解
printf("玩家赢\n");
else
printf("平局\n");
}
实现函数定义的.c文件
初始化函数
void game_init(char (*arr)[COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = ' '; //将二维数组元素全部初始化为‘ ’
}
}
}
打印函数
打印格式如下:
我们要实现这样的棋盘打印格式,就需要在每打印一个数组元素后打印一个‘|’,为了使显示的每个格子长和宽目测相等打印每个元素时应该在元素前面和后面各加上‘ ’,但是最后每一行的最后一个元素后不应该打印‘|’;并且每行和下一行之间也应该有分隔,可以打印‘—’并且每打印完一个‘—’后加上‘|’,最后一行下面不用打印分隔。
void game_print(char(*arr)[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 ", arr[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 game_player(char(*arr)[COL], int row, int col)
{
int x = 0;
int y = 0;
printf("请输入坐标:");
while (1) //设置一个while的死循环,满足条件以后通过break跳出
{
scanf("%d %d", &x, &y); //获取用户输入的坐标
if ((x > row) || (x < 1) || (y > col) || (y < 1)) //为什么要先判断坐标是否非法?
{
printf("坐标非法,请重新输入:");
}
else if (arr[x-1][y-1] != ' ') //先判断是否坐标非法,可以防止这里数组产生越界访问
{
printf("坐标已被占用,请重新输入:"); //判断坐标是否已被占用
}
else
{
arr[x-1][y-1] = '*'; //给数组坐标位置处赋值'*'表示玩家下棋
break; //跳出循环
}
}
}
电脑下棋
void game_computer(char(*arr)[COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
x = rand() % row; //随机取0~row-1的数字
y = rand() % col;
if (arr[x][y] == ' ') //如果坐标位置为空,则电脑下棋,坐标位置数组元素被赋值为'#'
{
arr[x][y] = '#';
break; //结束循环
}
}
}
判断输赢
三子棋输赢的结果有三种情况:玩家赢,电脑赢,平局
假设现在是5×5的棋盘
现在考虑如何判断每行是否有三个相同的字符连在一起,开始从第一个元素向后找两个元素,判断是否相等,如果不相等,接着从第二个元素向后找两个元素,判断是否相等,以此类推,但是从每行的倒数第二个元素开始就不能再往后找两个元素了,否则就会产生数组越界访问。
同样,判断每列是否有三个相同的字符连在一起,从每行的倒数第二个元素开始就不能再往后找两个元素了。
如何判断斜着是否有三个相同的字符连在一起呢?
比如说从右上角开始往左下角查找,从棋盘的第一行第一个元素开始找,看图发现当元素处在最后两列和最后两行时,都不能完整地找到它后面的两个元素,我们就不需要查找这两列两行开始是否满足要求了。
char game_win(char(*arr)[COL], int row, int col)//判断输赢
{
int i = 0; //这里为什么玩家赢和电脑赢不用区分呢?
int j = 0; //因为我们判断输赢只需要判断连着的三个字符一样的,返回值设置成这三个一样的字符就可以了
for (i = 0; i < row; i++)//具体是谁输谁赢,看函数的返回值就知道了
for (j = 0; j < col - 2; j++)//判断每行中是否有三个连在一起一样的字符
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i][j + 1]) && (arr[i][j] == arr[i][j + 2]))
return arr[i][j]; //返回相同的字符
for (i = 0; i < col; i++) //判断每列中是否有三个连在一起一样的字符
for (j = 0; j < row - 2; j++)
if ((arr[j][i] != ' ') && (arr[j + 1][i] == arr[j][i]) && (arr[j + 2][i] == arr[j][i]))
return arr[j][i];
for (i = 0; i < row-2; i++) //判断从左上角开始往右下角是否有三个连在一起一样的字符
{
for (j = 0; j < col - 2; j++)
{
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i + 1][j + 1]) && (arr[i][j] == arr[i + 2][j + 2]))
return arr[i][j];
}
}
for (i = 0; i < row - 2; i++) //判断从右下角开始往左上角是否有三个连在一起一样的字符
{
for (j = col; j > 1; j--)
{
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i + 1][j - 1]) && (arr[i][j] == arr[i + 2][j - 2]))
return arr[i][j];
}
}
for (i = row; i > 1; i--) //判断从左上角开始往右下角是否有三个连在一起一样的字符
{
for (j = 0; j < col - 2; j++)
{
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i - 1][j + 1]) && (arr[i][j] == arr[i - 2][j + 2]))
return arr[i][j];
}
}
for (i = row; i > 1; i--) //判断从左下角开始往右上角是否有三个连在一起一样的字符
{
for (j = col; j > 1; j--)
{
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i - 1][j - 1]) && (arr[i][j] == arr[i - 2][j - 2]))
return arr[i][j];
}
}
for (i = 0; i < row; i++) //判断是否还能继续下棋
for (j = 0; j < col; j++)
if (arr[i][j] == ' ') //就是判断棋盘中是否还有空位
return 'C'; //有空位返回'C'
return 'F'; //没人赢,棋盘还没有空位,说明是平局了
}
完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "trimoku.h"
void menu()
{
printf("***********三子棋**********\n");
printf("*********1.开始游戏********\n");
printf("*********0.退出游戏********\n"); //我们为什么要把0作为退出游戏的选项呢?详情请看下一内容讲解
}
void game()
{
srand(time(NULL)); //调用srand()函数确保每次电脑下棋的位置是随机的
char arr[ROW][COL] = {0}; //创建一个字符二维数组存放玩家和电脑下棋的数据
game_init(arr, ROW, COL); //初始化数组,交给一个函数完成就可以了
game_print(arr, ROW, COL); //按照一定格式打印数组
char c;
while (1)
{
printf("玩家下棋\n"); //玩家先下棋
game_player(arr, ROW, COL); //玩家下棋的具体实现函数
game_print(arr, ROW, COL); //打印一遍棋盘
c = game_win(arr, ROW, COL); //判断输赢的函数,判断输赢的结果有四种:平局,玩家赢,电脑赢,继续玩
if (c != 'C') //用'C'来表示继续玩,如果game()函数返回值不为‘C’(不继续玩),就要跳出循环
break;
printf("电脑下棋\n"); //电脑下棋
game_computer(arr, ROW, COL); //电脑下棋的具体实现函数
game_print(arr, ROW, COL); //打印一遍棋盘
c = game_win(arr, ROW, COL); //依然要判断是谁赢了
if (c != 'C')
break;
}
if (c == '#') //用'#'表示电脑下棋的符号,用'*'表示玩家下棋的符号
printf("电脑赢\n"); //这里赢的返回值为什么也使用的是他们各自的符号呢?⭐
else if (c == '*') //具体为什么,请看 判断输赢的函数 部分的讲解
printf("玩家赢\n");
else
printf("平局\n");
}
void test()
{
int input = 0;
do //这里采用do while结构,因为无论如何都需要执行一遍菜单函数
{
menu();
printf("请选择是否要开始游戏:\n");
scanf("%d",&input);
while ((input != 1) && (input != 0))//这里用来判断玩家是否输入的正确
{
printf("选择错误,请重新输入:\n");
scanf("%d", &input);
}
switch (input) //使用switch语句进行玩家想进行的操作
{
case 1:
game();
break;
case 0:
printf("退出游戏\n"); //选择0退出游戏
break;
}
} while (input); //同时因为input=0,do while语句后条件为假,结束循环⭐
}
int main()
{
test();
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "trimoku.h"
void game_init(char (*arr)[COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = ' '; //将二维数组元素全部初始化为‘ ’
}
}
}
void game_print(char(*arr)[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 ", arr[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 game_player(char(*arr)[COL], int row, int col)
{
int x = 0;
int y = 0;
printf("请输入坐标:");
while (1) //设置一个while的死循环,满足条件以后通过break跳出
{
scanf("%d %d", &x, &y); //获取用户输入的坐标
if ((x > row) || (x < 1) || (y > col) || (y < 1)) //为什么要先判断坐标是否非法?
{
printf("坐标非法,请重新输入:");
}
else if (arr[x-1][y-1] != ' ') //先判断是否坐标非法,可以防止这里数组产生越界访问
{
printf("坐标已被占用,请重新输入:"); //判断坐标是否已被占用
}
else
{
arr[x-1][y-1] = '*'; //给数组坐标位置处赋值'*'表示玩家下棋
break; //跳出循环
}
}
}
void game_computer(char(*arr)[COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
x = rand() % row; //随机取0~row-1的数字
y = rand() % col;
if (arr[x][y] == ' ') //如果坐标位置为空,则电脑下棋,坐标位置数组元素被赋值为'#'
{
arr[x][y] = '#';
break; //结束循环
}
}
}
char game_win(char(*arr)[COL], int row, int col)//判断输赢
{
int i = 0; //这里为什么玩家赢和电脑赢不用区分呢?
int j = 0; //因为我们判断输赢只需要判断连着的三个字符一样的,返回值设置成这三个一样的字符就可以了
for (i = 0; i < row; i++)//具体是谁输谁赢,看函数的返回值就知道了
for (j = 0; j < col - 2; j++)//判断每行中是否有三个连在一起一样的字符
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i][j + 1]) && (arr[i][j] == arr[i][j + 2]))
return arr[i][j]; //返回相同的字符
for (i = 0; i < col; i++) //判断每列中是否有三个连在一起一样的字符
for (j = 0; j < row - 2; j++)
if ((arr[j][i] != ' ') && (arr[j + 1][i] == arr[j][i]) && (arr[j + 2][i] == arr[j][i]))
return arr[j][i];
for (i = 0; i < row-2; i++) //判断从左上角开始往右下角是否有三个连在一起一样的字符
{
for (j = 0; j < col - 2; j++)
{
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i + 1][j + 1]) && (arr[i][j] == arr[i + 2][j + 2]))
return arr[i][j];
}
}
for (i = 0; i < row - 2; i++) //判断从右下角开始往左上角是否有三个连在一起一样的字符
{
for (j = col; j > 1; j--)
{
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i + 1][j - 1]) && (arr[i][j] == arr[i + 2][j - 2]))
return arr[i][j];
}
}
for (i = row; i > 1; i--) //判断从左上角开始往右下角是否有三个连在一起一样的字符
{
for (j = 0; j < col - 2; j++)
{
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i - 1][j + 1]) && (arr[i][j] == arr[i - 2][j + 2]))
return arr[i][j];
}
}
for (i = row; i > 1; i--) //判断从左下角开始往右上角是否有三个连在一起一样的字符
{
for (j = col; j > 1; j--)
{
if ((arr[i][j] != ' ') && (arr[i][j] == arr[i - 1][j - 1]) && (arr[i][j] == arr[i - 2][j - 2]))
return arr[i][j];
}
}
for (i = 0; i < row; i++) //判断是否还能继续下棋
for (j = 0; j < col; j++)
if (arr[i][j] == ' ') //就是判断棋盘中是否还有空位
return 'C'; //有空位返回'C'
return 'F'; //没人赢,棋盘还没有空位,说明是平局了
}
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#define COL 3
#define ROW 3
void game_init(char (*arr)[COL], int row, int col);
void game_print(char (*arr)[COL], int row, int col);
void game_player(char(*arr)[COL], int row, int col);
void game_computer(char(*arr)[COL], int row, int col);
char game_win(char(*arr)[COL], int row, int col);