使用C语言在控制台窗口实现不同模式下的三子棋游戏
1.简单模式下电脑任意下子
2.复杂模式下电脑能对玩家获胜进行阻拦
存储结构:二维数组
交互方式:玩家输入坐标位置
我们将项目分成 game.h,game.c,test.c 来让项目更清晰
功能模块:
1.游戏规则 (普通的printf实现)
2.游戏菜单 (do while,switch case)
3.选择模式 (函数实现,简单返回1,复杂返回2)
4.选择先手 (函数实现,玩家返回1,电脑返回2)
5.初始棋盘 (二维数组,for循环)
6.打印棋盘 (二维数组,for循环)
7.玩家下棋 (二维数组 ,scanf)
8.电脑下棋 (随机数生成,取胜,拦截)
9.判断输赢 (函数实现,返回不同字符,代表不同结果)
各个模块都较容易实现,其中复杂模式电脑下棋的取胜与拦截的思路如下:
电脑再下一子取得胜利时,应优先取胜,通过扫描棋盘,记录行,列,对角线已有两子时返回能连成线的坐标值(通过指针实现)。
如再下一子不能取得胜利时,就对棋手的取胜进行阻拦,用上述的函数,添加一个棋子字符参数,通过改变参数,实现记录已棋手已有两子时返回能连成线的坐标值。
除些之外的情况可通过随机数,随机生成电脑棋子的位置。
不同功能模块的实现还会用到其他的工具函数,将会在下面进行具体介绍。
相信看到这里大家应该有了思路,现在来看一下具体的实现。
将主要模块的函数声明和要用到的系统头文件写在game.h中
#pragma once
#include
#include
#include
#define ROW 3
#define COL 3
#define KEY 2
//选择模式
int Model();
//选择先手
int Offensive();
//初始化棋盘
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,int model);
//判断输赢
/*返回
* ‘*’---玩家嬴
* ‘#’---电脑嬴
* ‘D’---平局
* ‘C’---继续
*/
char VictoryJudgment(char board[ROW][COL], int row, int col);
用宏定义#define 定义的不仅仅是常量,更是我们打开的格局,这样做更便于我们更改常量,不会在想改动时修改大量代码。
在game.c中我们实现具体的功能模块
要注意在文件开头引用game.h头文件
选择模块实现
void model_choose()
{
printf("***************************************************************\n");
printf("***** 1.Easy Mode *****\n");
printf("***** 2.Complex mod *****\n");
printf("***************************************************************\n");
}
int Model()
{
char choose = '0';
do {
model_choose();
printf("Please select:>");
scanf("%c", &choose);
getchar();
switch (choose)
{
case '1':
return 1;
case '2':
return 2;
default:
printf("Selection error\n");
break;
}
} while (choose!='1' && choose != '2');
}
选择先手模块与上述相同这里不做赘述
打印游戏规则相信大家都会,只要会打helloworld的都会打,初始化棋盘就是将二维数组中每个字符元素置成空格此处也省略。
打印棋盘实现如下:
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i, j = 0;
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");
}
}
}
打印效果如下图
如果将行和列的常量改变,此代码依旧能打印出相应的棋盘。
将ROW,和COL改为5后
不过这应该就不算三子棋了(手动滑稽)
下面为玩家下棋模块:
void PlayerMove(char board[ROW][COL], int row, int col)
{
printf("Players:>\n");
int x, y = 0;
while (1)
{
scanf("%d %d", &x, &y);
getchar();//防止死循环
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("This coordinate is occupied, please re-enter\n");
}
}
else
{
printf("Illegal coordinates, please re-enter!\n");
}
}
}
getchar();可以在不小心输入非法字符比如’a’时防止程序出现死循环。
下面着重讲一下电脑下棋的部分,先看一下代码
void ComputerMove(char board[ROW][COL], int row, int col,int model)
{
int x, y = 0;
printf("Computer:>\n");
if (1 == model)//简单模式
{
while (1)
{
x = rand() % ROW;
y = rand() % COL;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
else//复杂模式
{
int* a = &x;
int* b = &y;
char c = '0';
while (1)
{ //优先取胜
c = '#';
//行取胜
if (1 == LineWin(board, row, col, a, b,c))
{
board[*a][*b] = '#';
break;
}
//列取胜
if (1 == ColumnWin(board, row, col, a, b,c))
{
board[*a][*b] = '#';
break;
}
//对角线取胜
if (1 == DiagonalWin(board, row, col, a, b,c))
{
board[*a][*b] = '#';
break;
}
//优先拦截
c = '*';
//行拦截
if (1 == LineWin(board, row, col, a, b, c))
{
board[*a][*b] = '#';
break;
}
//列拦截
if (1 == ColumnWin(board, row, col, a, b, c))
{
board[*a][*b] = '#';
break;
}
//对角线拦截
if (1 == DiagonalWin(board, row, col, a, b, c))
{
board[*a][*b] = '#';
printf("%d %d@\n", *a, *b);
break;
}
x = rand() % ROW;
y = rand() % COL;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
}
其中有一些还未实现的函数,思路我前面已经讲过,现在看一下具体过程。
int LineWin(char board[ROW][COL], int row, int col, int* a, int* b,char c )
{
int i,j = 0;
int count = 0;
for (i = 0; i < row; i++)
{
count = 0;
*a = -1;
*b = -1;
for (j = 0; j < col; j++)
{
if (board[i][j] == c)
{
count++;
}
if (board[i][j] == ' ')
{
*a = i;
*b = j;
}
}
if (count == KEY && *a != -1 && *b != -1)
{
return 1;
}
}
return 0;
}
ColumnWin函数与LineWin函数原理相同,LineWin函数是想求每一行可能取胜的位置,ColumnWin函数则是求按列的。KEY值定为2是因为对面都下两个子啦,再不拦截不就输了嘛- -!
下面是对角线判定函数:
int DiagonalWin(char board[ROW][COL], int row, int col, int* a, int* b,char c)
{
int i = 0;
int count = 0;
*a = -1;
*b = -1;
for (i = 0; i < row; i++)
{
if (board[i][i] == c)
{
count++;
}
if (board[i][i] == ' ')
{
*a = i;
*b = i;
}
if (count == KEY && *a != -1 && *b != -1)
{
return 1;
}
}
count = 0;
*a = -1;
*b = -1;
for (i = 0; i < row; i++)
{
if( board[i][COL - 1 - i] == c)
{
count++;
}
if (board[i][COL - 1 - i] == ' ')
{
*a = i;
*b = COL-1-i;
}
if (count == KEY && *a != -1 && *b != -1)
{
return 1;
}
}
return 0;
}
这种如果一时看不出来,拿张纸把对角线坐标写出来很快就能知道规律了,看着可能不如简单写法的代码简练,但是家人们要把格局打开,写死了就没意思啦。
判断输赢模块也是分行、列、对角线判断的思路。
char VictoryJudgment(char board[ROW][COL], int row, int col)
{
int i,j = 0;
int flag = 0;
//判断行
for (i = 0; i < row; i++)
{
flag = 0;
for (j = 0; j < col; j++)
{
if (board[i][0] != board[i][j]||board[i][j]==' ')
flag = 1;
}
if (flag == 0)
{
return board[i][0];
}
}
flag = 0;
//判断列
for (i = 0; i < col; i++)
{
flag = 0;
for (j = 0; j < row; j++)
{
if (board[0][i] != board[j][i] || board[j][i] == ' ')
flag = 1;
}
if (flag == 0)
{
return board[0][i];
}
}
flag = 0;
//判断对角线
for (i = 0; i < row; i++)
{
if (board[0][0] != board[i][i] || board[i][i] == ' ')
flag = 1;
}
if (flag == 0)
{
return board[0][0];
}
flag = 0;
for (i = 0; i < row; i++)
{
if (board[0][COL - 1] != board[i][COL - 1 - i] || board[i][COL-1-i] == ' ')
flag = 1;
}
if (flag == 0)
{
return board[0][COL-1];
}
//判断平局
if (1 == is_full(board, row, col))
{
return 'D';
}
//继续
return 'C';
}
is_full函数就是看数组中是否还有空格,有就是棋盘不满,继续,否则就是棋盘已满,平局。
各个模块都实现完了,来看看有main函数的test.c部分(其实我写的时候,是看要用到什么函数再去game.c实现的,缺什么补什么,但是我觉得换种方式呈现给大家更好一点儿)。
void test()
{
char input = '0';
char choose = '0';
char attacker = '0';
rule();
do
{
menu();
printf("Please select:>");
scanf("%c", &input);
getchar();
switch (input)
{
case '1':
choose = Model();
attacker = Offensive();
game(choose,attacker);
break;
case '0':
printf("Exit the game\n");
break;
default:
printf("Selection error\n");
break;
}
} while (input != '0');
}
int main()
{
srand((unsigned int)time(NULL));
test();
return 0;
}
在这个菜单框架里没有实现game();函数,因为为了让程序清晰,把它移出去了。
void game(int choose,int attacker)
{
//用字符的二维数组存储,玩家下棋是"*",电脑下棋是"#"
char board[ROW][COL]={0};//棋盘初始数组应该是空格
InitBoard(board,ROW,COL);//初始化棋盘
//打印棋盘
DisplayBoard(board,ROW,COL);
printf("Your chess piece is \"*\"\n");
//下棋
char ret = 0;
while (1)
{
//棋手先下
if (1==attacker )
{
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = VictoryJudgment(board, ROW, COL);
if (ret != 'C')
{
break;
}
ComputerMove(board, ROW, COL,choose);
DisplayBoard(board, ROW, COL);
ret = VictoryJudgment(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
else//电脑先下
{
ComputerMove(board, ROW, COL,choose);
DisplayBoard(board, ROW, COL);
ret = VictoryJudgment(board, ROW, COL);
if (ret != 'C')
{
break;
}
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = VictoryJudgment(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
}
if (ret == '*')
{
printf("Victory!\n");
}
else if (ret == '#')
{
printf("Defeat!\n");
}
else
{
printf("Draw!you're on the same level\n");
}
}
而game函数也是为了充分的使用前面实现的功能模块,就是用造好的零部件组装成我们要的东西。