三子棋:是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。
本程序可修改为多子棋,只需要修改#define定义的常量大小就行
#define ROW 3
#define COL 3
#define ROW 10
#define COL 10
程序目录:
一、创建头文件文件、源文件(整体代码)
二、游戏基本逻辑(每部分代码的分析)
1.游戏主页
2.玩家选择玩游戏还是退出游戏
3.初始化棋盘
4.打印棋盘
5.玩家下棋
6.判断输赢
7.电脑下棋
8.判断输赢
三、游戏结果
1.通过判断显示获胜方
2.打印游戏主页,是否再玩一次游戏
程序的具体内容:
一、先创建一个头文件、两个源文件,将代码有规律有逻辑的存放,方便调试
头文件:game.h 用于游戏代码中的函数(包括库函数、自定义函数)声明、符号定义
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3
//初始化棋盘
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);
//判断输赢
char Iswin(char board[ROW][COL], int row, int col);
//判断行
char Winhang(char board[ROW][COL], int row, int col);
//判断列
char Winlie(char board[ROW][COL], int row, int col);
//判断对角线
char Windjx(char board[ROW][COL], int row, int col);
//判断平局
int IsFull(char board[ROW][COL], int row, int col);
源文件:game.c 用于游戏代码的实现,在其中写入自定义函数的相关内容
#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 Displayboard(char board[ROW][COL], int row, int col)
{
printf("\n");
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
//分割信息“---|---|---”
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
//玩家下棋
void Playermove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("\n");
printf("玩家请下棋\n");
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 Computermove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑下棋\n");
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
//判断输赢
char Iswin(char board[ROW][COL], int row, int col)
{
char ret = 0;
//判断行赢 赢了返回temp 没赢返回s
ret = Winhang(board, ROW, COL);
if (ret != 's')
{
return ret;
}
//判断列赢
ret = Winlie(board, ROW, COL);
if(ret!='s')
{
return ret;
}
//判断对角线
ret = Windjx(board, ROW, COL);
if (ret != 's')
{
return ret;
}
//判断平局
if (IsFull(board, row, col))
{
return 'q';
}
return 'c';
}
//判断行
char Winhang(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
char temp = 0;
again:
while (i < row)
{
temp = board[i][0];
if (board[i][0] != ' ')
{
while (j < col)
{
if (board[i][j] == board[i][0])
{
j++;
}
else
{
i++;
j = 0;
goto again;
}
}
return temp;
}
else
{
i++;
}
}
return 's';
}
char Winlie(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
char temp = 0;
again:
while (j<col)
{
temp = board[0][j];
if (board[0][j] != ' ')
{
while (i<row)
{
if (board[i][j] == board[0][j])
{
i++;
}
else
{
j++;
i = 0;
goto again;
}
}
return temp;
}
else
{
j++;
}
}
return 's';
}
//判断对角线
char Windjx(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
char temp = 0;
//第一条对角线
//for (i = 0, j = 0; i < row, j < col; i++, j++)
while(i < row && j < col)
{
if (board[0][0]!=' ')
{
temp = board[0][0];
if (board[0][0] == board[i][j])
{
i++;
j++;
}
else
{
goto again;
}
}
else
{
goto again;
}
}
return temp;
//第二条对角线
again:
i = 0;
j = COL-1;
temp = board[0][j];
while(i<row && j<col && j>=0)
{
if (temp != ' ')
{
if (temp == board[i][j])
{
i++;
j--;
}
else
{
return 's';
}
}
else
{
return 's';
}
}
return temp;
}
//判断平局
int IsFull(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++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
源文件:test.c 用于测试游戏的逻辑
#include"game.h"
void game()
{
char ret = 0;
char board[ROW][COL] = { 0 };
//初始化棋盘
Initboard(board, ROW, COL);
//打印棋盘
Displayboard(board, ROW, COL);
while (1)
{
//玩家下棋
Playermove(board, ROW, COL);
Displayboard(board, ROW, COL);
// 判断输赢 返回*玩家赢 返回#电脑赢 返回q平局 返回c继续
ret = Iswin(board, ROW, COL);
if (ret != 'c')
{
break;
}
//电脑下棋
Computermove(board, ROW, COL);
Displayboard(board, ROW, COL);
//判断输赢
ret = Iswin(board, ROW, COL);
if (ret != 'c')
{
break;
}
}
if (ret == '*')
{
printf("恭喜你赢了!!!\n");
Displayboard(board, ROW, COL);
}
if (ret == '#')
{
printf("电脑赢了\n");
Displayboard(board, ROW, COL);
}
if (ret == 'q')
{
printf("平局了\n");
Displayboard(board, ROW, COL);
}
}
void menu()
{
printf(" 三子棋\n");
printf("********************************\n");
printf("*********0.exit 1.paly*********\n");
printf("********************************\n");
}
int main()
{
srand((unsigned int)time(NULL));//设置随机数的的生成起点
int input = 0;
do
{
menu();
printf("请选择:>>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
二、游戏的逻辑
1.游戏主页:需要给玩家两个选择(0.退出游戏 1.玩游戏),我们将游戏主页写入函数中,需要时直接调用,避免代码重复书写带来麻烦。
//游戏主页
void menu()
{
printf(" 三子棋\n");
printf("********************************\n");
printf("*********0.exit 1.paly*********\n");
printf("********************************\n");
}
玩家选择退出游戏:0.exit 对应的退出程序
玩家选择玩游戏:1.play 对应进入游戏主程序
switch语句来实现:对于两种选择0和1,有不同的答案,对于输入其它数字,则输入错误,需要重新选择,因此可以使用switch语句来实现。
do......while循环:由于必须要有一个正确的选择,所以需要循环此过程,直到选择出一个结果,此处我们选择do......while循环,此循环的特点是至少执行循环一次,即进入游戏就得选择,循环的判断部分我们用玩家输入的input来作为判断,用它的好处在于除了选择 0 ,输入的其它正整数(除0外的其它值为真)都将会执行该循环,直到做出正确的选择。
int main()
{
srand((unsigned int)time(NULL));//设置随机数的的生成起点
int input = 0;
do
{
menu();
printf("请选择:>>");
scanf("%d", &input);
switch (input)
{
case 1:
game(); //进入游戏主程序
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
2.初始化棋盘和棋子:三子棋的棋盘是一个九宫格,首先需要一个二维数组来存放玩家与电脑下的棋子,先将该二维数组初始化为0,用“ * ”表示玩家棋子,用“ # ”表示电脑棋子,棋盘的初始化和打印也需要运用到二维数组。下面我将用自定义函数一一实现各部分的内容。
初始化棋盘(函数):Initboard(board, ROW, COL);
函数传参内容包括:二维数组board[ ][ ] #define定义的标识符常量ROW(行数)、COL(列数)
函数定义:
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] = ' ';
}
}
}
函数中用char board[ROW][COL]接收传递过来二维数组,int row、int col分别接收传过来的ROW、COL。先将二维数组初始化,全部放入'空格'。
3.打印棋盘:棋盘由棋子部分、分割部分(界线)两部分组成
将表格细分开为:“ %c | %c | %c ”、“---|---|---”,可以观察到:最后一个字符后没有“|”,而在最后一行没有分割线“---”,仅有“|”,我们针对每一层次需要打印的内容用 if 语句来约束实现。
打印棋盘:Displayboard(board, ROW, COL);
函数定义:
void Displayboard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
//分割信息“---|---|---”
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
4.玩家下棋:下棋的过程是一个循环的过程“ 玩家下棋→打印棋盘→判断输赢→电脑下棋→打印棋盘→判断输赢 ”,并且该循环为死循环,直到分出胜负后才满足跳出循环的条件,此处用循环while(1)来实现。无论是对于玩家下棋还是电脑下棋,都需要判断下棋的位置是否是空的,若该位置有棋子了,则不能在此再落子;若输入的坐标超出范围了,则需要提醒玩家输入坐标有错,需要重新输入。
玩家下棋:Playermove(board, ROW, COL);
函数定义:
void Playermove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家请下棋\n");
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");
}
}
}
玩家通过输入对应的行数、列数确定落子的位置,玩家棋子用“ * ”表示。
5.判断输赢:分别对行、列、对角线进行判断,若有三子相同连在一起则为赢。此处需要用一个变量来承接返回值,用来反馈判断结果,再输出结果。
返回值“ c ”:表示暂无胜负,继续程序
返回值“ * ”:表示玩家赢了
返回值“ # ”:表示电脑赢了
返回值“ q ”:表示平局
判断输赢:Iswin(board, ROW, COL);
函数定义:
char Iswin(char board[ROW][COL], int row, int col)
{
char ret = 0;
//判断行赢 赢了返回temp 没赢返回s
ret = Winhang(board, ROW, COL);
if (ret != 's')
{
return ret;
}
//判断列赢
ret = Winlie(board, ROW, COL);
if(ret!='s')
{
return ret;
}
//判断对角线
ret = Windjx(board, ROW, COL);
if (ret != 's')
{
return ret;
}
return 'c';
}
在该函数中又包含了三个函数,分别用于判断行、列、对角线的情况,均用变量ret承接返回值。
向函数Iswin(board, ROW, COL)反馈其返回值
返回“ s ”:表示没有赢的情况
返回“ temp ”:其值可能为“ * ”、“ # ”,分别表示玩家、电脑满足判断的条件
判断行:Winhang(board, ROW, COL);
函数定义:
char Winhang(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
char temp = 0;
again:
while (i < row)
{
temp = board[i][0];
if (board[i][0] != ' ')
{
while (j < col)
{
if (board[i][j] == board[i][0])
{
j++;
}
else
{
i++;
j = 0;
goto again;
}
}
return temp;
}
else
{
i++;
}
}
return 's';
}
判断思路:先判断每一行的第一个元素是否为空格,若为空格则直接跳转到下一行的判断;若不为空格,再依次判断本行后面的元素是否与第一个元素相同,若中途有与第一个元素不同的元素出现,则直接跳出while循环进行下一行的判断;若本行元素均与本行的第一个元素相同,则跳出循环,返回本行的第一个元素。
判断列:Winlie(board, ROW, COL);
函数定义:
char Winlie(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
char temp = 0;
again:
while (j<col)
{
temp = board[0][j];
if (board[0][j] != ' ')
{
while (i<row)
{
if (board[i][j] == board[0][j])
{
i++;
}
else
{
j++;
i = 0;
goto again;
}
}
return temp;
}
else
{
j++;
}
}
return 's';
}
判断思路:先判断每一列的第一个元素是否为空格,若为空格则直接跳转到下一列的判断;若不为空格,再依次判断本列后面的元素是否与第一个元素相同,若中途有与第一个元素不同的元素出现,则直接跳出while循环进行下一列的判断;若本列元素均与本行的第一个元素相同,则跳出循环,返回本列的第一个元素。
判断对角线:Windjx(board, ROW, COL);
函数定义:
char Windjx(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
char temp = 0;
//第一条对角线
//for (i = 0, j = 0; i < row, j < col; i++, j++)
while(i < row && j < col)
{
if (board[0][0]!=' ')
{
temp = board[0][0];
if (board[0][0] == board[i][j])
{
i++;
j++;
}
else
{
goto again;
}
}
else
{
goto again;
}
}
return temp;
//第二条对角线
again:
i = 0;
j = COL-1;
temp = board[0][j];
while(i<row && j<col && j>=0)
{
if (temp != ' ')
{
if (temp == board[i][j])
{
i++;
j--;
}
else
{
return 's';
}
}
else
{
return 's';
}
}
return temp;
}
判断思路:对角线有两条对角线
第一条对角线:先判断对角线第一个元素board [0][0]是否为空格,若为空格则直接跳转到第二条对角线的判断;若不为空格,则判断该对角线上的第二个元素board [1][1]是否与第一个元素相同,再依次判断本对角线后面的元素是否与第一个元素相同;若均相同则返回第一个元素;若不同,则跳转到第二条对角线的判断。
第二条对角线:先判断对角线第一个元素board [0][COL-1](若为三子棋就是board [0][2])是否为空格,若为空格则返回“ s ”;若不为空格,则判断该对角线上的第二个元素board [i][j](若为三子棋则对应board [1][1])是否与第一个元素相同,再依次判断本对角线后面的元素是否与第一个元素相同;若均相同则返回第一个元素;若不同,则返回“ s ”。
判断平局:IsFull(board, row, col);
函数定义:
int IsFull(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++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
判断思路:经过行、列、对角线的判断,若棋盘上再也没有空格存在,则该情况为平局。用if()语句来控制返回值,若括号内值为1,则返回“ q ”;若括号内值为0,则不执行if()语句,直接按照程序顺序返回“ c ”。
6.电脑下棋:此处需要生成随机数表示下棋是随机下,若想电脑掌握更好的下棋步骤,需要对下棋步骤进行要求,此过程复杂,暂时不编写。
电脑下棋:Computermove(board, ROW, COL);
函数定义:
void Computermove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑下棋\n");
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
用函数rand()生成随机数,为了让随机数满足棋盘中每个位置的坐标要求,若为三子棋则需要随机生成0 、1 、2,用随机数分别对行数和列数取余就行,为而调用rand()函数就需要调用srand()函数来生成随机数的起点,在调用srand()函数时需要使用time()函数来形成时间戳,这样就能成功生成随机数,而这样的函数只需要调用一次就行了,所以将它放入了main函数。对time()函数传入一个空指针NULL,将其返回值用强制类型转换(unsigned int)为无符号的整形。
再对电脑的棋子进行判断
三、游戏结果:对判断输赢的返回值进行判断最后的赢家,并打印出结果和游戏主页,让玩家在次选择是否再玩一次。