三子棋
一.三子棋介绍
三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。本篇文章将根据所学数组内容进行应用,设计出一个简易的三子棋游戏
二.设计思路
(一)设计出一个菜单引导玩家进行游戏
(二)设计出游戏大体框架
1.设计菜单实现程序(选择开始游戏/退出游戏,输错提示)
2.创建一个3×3的数组作为棋盘;
3.初始化棋盘并打印棋盘;
4.设计下棋步骤,包括玩家下棋和电脑下棋;
5.设计判断输赢、和棋函数;
三.设计程序
我们上手来写时,发现这是一段非常长的代码,在一个文件里写出来可读性不高。这里建议大家分文件去写,还能保证程序的封装性,可读性和易维护性。我们分三个文件来写:
test.c 测试游戏的程序
game.c 游戏函数的具体实现
game.h 游戏函数的所有声明,头文件,宏定义等。
1.设计菜单
设计一个函数打印菜单
//写在test.c里
void menu()
{
printf("**************************************\n");
printf("********** 三子棋游戏 ************\n");
printf("********** 选择1:开始游戏 ***********\n");
printf("********** 选择0:退出游戏 ***********\n");
printf("**************************************\n");
}
菜单功能包括输入“1”:“开始游戏”,输入“0”:“退出游戏”,输入其他就要提示输错。如果玩完一把后不用退出程序重新开始,我们将这个选择程序放在一个循环中。菜单要重复打印,所以把menu()放在循环中,因为最开始要先打印一次菜单,所以我们用do…while…循环。
int main()
{
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
测试:
2.设计棋盘
具体设计棋盘时,我们发现我们先要创建一个棋盘,并初始化,然后还要实现打印。
1.初始化棋盘
我们开始写初始化棋盘函数:
写在game.c中的:
void InitBoard(char board[3][3], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
写在game.h中的:
void InitBoard(char board[3][3], int row, int col);
我们这样写发现后续若要改为五子、十子等棋,N子棋时,程序中的3全都要改一遍极其麻烦,于是我们可以宏定义行和列,这样只需要改这两个定义即可转换成多子棋。
game.h中写:
#define ROW 3
#define COL 3
修改上述函数:
写在game.c中的:
void InitBoard(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++)
{
board[i][j] = ' ';
}
}
}
写在game.h中的:
void InitBoard(char board[ROW][COL], int row, int col);
2.打印棋盘
我们这里提供的方法是将数据和分割线分开打印
Tip:因为我们不打印最后一个“|”,所以打印“|”前需要进行判断。
game.h中声明:
void Display(char board[ROW][COL], int row, int col);
game.c中实现:
void Display(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");
}
}
}
测试代码:
//写在test.c中的
void game()
{
char board[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(board, ROW, COL);
//打印棋盘
Display(board, ROW, COL);
}
测试
3.设计下棋步骤
1.玩家下棋
玩家此时输入一个坐标表示下棋的位置,行要大于等于1小于等于3,
列要大于等于1小于等于3。
这时下的坐标会有几种情况:
1.此坐标被占用,已有棋子,提示"坐标已有子,请重新输入"
2.此坐标非法,即超出了棋盘(3×3)范围,提示"坐标非法,请重新输入"
3.此坐标为空格,可以下棋
game.c中实现:
//玩家下棋
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);
//printf("玩家下棋:\n");
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x - 1][y - 1] == ' ')//可以落子
{
board[x - 1][y - 1] = '*';
break;
}
else//已有子,不能落子
{
printf("坐标已有子,请重新输入\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
}
我们这时再来测试一下:
测试代码:
void game()
{
char board[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(board, ROW, COL);
//打印棋盘
Display(board, ROW, COL);
while (1)
{
Playermove(board, ROW, COL);
Display(board, ROW, COL);//打印棋盘来看我们输入结果
}
}
2.电脑下棋
我们这里采用的方法是电脑随机下棋,这种方法较为简单地实现电脑下棋。当然我们也可以设计进攻算法和防守算法来强化电脑,此处我们仅是实现简易的三子棋,故不多赘述。
我们对电脑x,y坐标生成随机数,依然是使用我们的rand,srand函数,详细使用方法在猜数字游戏中已经讲过。
game.c 中实现:
//电脑下棋
void Computermove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑下棋:\n");
while (1)
{
x = rand() % row;//生成0~2的数
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
test.c 中测试:
void game()
{
char board[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(board, ROW, COL);
//打印棋盘
Display(board, ROW, COL);
while (1)
{
Playermove(board, ROW, COL);
Display(board, ROW, COL);
Computermove(board, ROW, COL);
Display(board, ROW, COL);
}
}
int main()
{
srand((unsigned int)time(NULL));//
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
测试:
4.设计判断输赢函数
赢的条件是行或列或对角线三子连在一起。
1.当玩家赢了返回"*“;
2.当电脑赢了返回”#“;
3.当棋盘满了且胜负未分返回"Q”
4.若胜负未分且棋盘未满则继续,返回‘C’。
判断棋盘是否满了还要设计一个函数来判断:
game.c中实现:
//判断棋盘是否满了
int Isfull(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++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
判断输赢函数实现:
game.c中实现:
//判断输赢
//玩家:*
//电脑赢:#
//平局:Q
//继续:C
char Iswin(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 - 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, 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 = col - 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];
}
//平局
if (Isfull(board, row, col)==1)
{
return 'Q';
}
//继续
return 'C';
}
Tip:我们用循环的方式一个个访问数组里的元素,判断是第几种情况。
我们可以使用*/#
来判断输赢,这时无论是玩家赢还是电脑赢返回时直接返回board[i][j]就行。而不需要单独判断是玩家赢了还是电脑赢了。
四.调试实例
五.完整代码参考
game.h:
#define _CRT_SECURE_NO_WARNINGS 1
#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 Display(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);
int Isfull(char board[ROW][COL], int row, int col);
char Iswin(char board[ROW][COL], int row, int col);
game.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化棋盘
void InitBoard(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++)
{
board[i][j] = ' ';
}
}
}
//打印棋盘
void Display(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");
}
}
}
//玩家下棋
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);
//printf("玩家下棋:\n");
if (x >= 1 && x <= row && y >= 1 && 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;
}
}
}
//判断棋盘是否满了
int Isfull(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++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
//判断输赢
//玩家:*
//电脑赢:#
//平局:P
//继续:C
char Iswin(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 - 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, 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 = col - 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];
}
//平局
if (Isfull(board, row, col)==1)
{
return 'Q';
}
//继续
return 'C';
}
test.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("**************************************\n");
printf("********** 三子棋游戏 ************\n");
printf("********** 选择1:开始游戏 ***********\n");
printf("********** 选择0:退出游戏 ***********\n");
printf("**************************************\n");
}
void game()
{
char board[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(board, ROW, COL);
//打印棋盘
Display(board, ROW, COL);
//下棋步骤
char ret = 0;
while (1)
{
//玩家下棋
Playermove(board, ROW, COL);
Display(board, ROW, COL);
ret = Iswin(board, ROW, COL);
if (ret != 'C')
break;
//电脑下棋
Computermove(board, ROW, COL);
Display(board, ROW, COL);
ret = Iswin(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '*')
printf("玩家赢\n");
else if (ret == '#')
printf("电脑赢\n");
else
printf("平局\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
N子棋
总结
此次的三子棋设计实验,能提高我们对分文件处理程序设计的能力,在本篇文章的程序中通过修改宏定义的ROW,COL即可实现多种棋程序的转换,我们成功的实现了多子棋,提高了程序的易维护性,代码还有许多不足还需日后完善,还望各位大佬多多提出建议。