实现三子棋,在写代码的时候要写很多行,所以需要把代码分模块管理。
- test.c //测试游戏逻辑
- game.c //游戏代码的实现
- game.h //游戏代码的声明(函数声明,符号定义)
把最基本的逻辑写一下,首先至少玩一把,上来就需要打印个菜单(1玩0退出),用do while循环,并且做相应的提示(菜单)让玩家选择,仔根据做出的选择做出响应,这时可以使用switch语句来实现。
void menu()
{
puts("****************************");
puts("****** 1.开始 ******");
puts("****** 0.退出 ******");
puts("****************************");
}
int main()
{
int input = 0;
do
{
menu();
printf("请输入选项:");
scanf("%d", &input);
switch (input)
{
case 1:
puts("三子棋");
break;
case 0:
puts("退出游戏");
break;
default:
puts("输入错误!\n");
break;
}
} while (input);
return 0;
}
这里测试出没有bug的时候就要来实现三子棋了,现在case 1中封装game函数,来实现游戏的功能,三子棋需要一个3*3的棋盘也就是二维数组,玩家和电脑下棋分别需要不同的符号来标记并显示 ,因此是一个char类型的二维数组。
//此函数调用放在case 1中
void game()
{
char board[3][3] = { 0 };
}
接下来就是下棋,需要打印一个3*3的棋盘。
然后玩家走,电脑走,同时还要判断输赢。
到这里就有了几个功能:初始化棋盘、打印棋盘、玩家走、电脑走和判断输赢。
最开始的棋盘应该是全部初始化为空格并打印,等玩家或电脑走的时候才开始显示字符。
void game()
{
char board[3][3] = { 0 };
//初始化棋盘函数
Initboard(board, 3, 3);
}
把游戏功能函数声明放在头文件game.h中。
但是注意,如果参数全都是3,就把代码写死了,就是三子棋了,如果以后想改成五子棋十子棋就非常麻烦,涉及到3的地方全都要修改。
这时可以在头文件里用define来定义棋盘的行和列:
以后如果先修改棋盘,直接在这里修改就非常方便。
接着就是初始化棋盘函数的定义:
//全部初始化为空格
void Initboard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
初始化之后观察里面的元素是否打印正确,需打印一下棋盘,每一次下棋都需要打印一次棋盘:
void game()
{
char board[ROW][COL] = { 0 };
//初始化棋盘函数
Initboard(board, ROW, COL);
//打印棋盘
Displayboard(board, ROW, COL);
}
依然在game函数里调用,把声明和定义分别放在game.h头文件和game.c源文件中(参考初始化棋盘格式)。
打印函数的实现:
//打印棋盘
void Displayboard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
//每打印一个元素就用|隔开
//|的数量应该是元素-1
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
//作为每行元素的分割
//打印次数也是元素-1次
if (i < row - 1)
{
for (int x = 0; x < row; x++)
{
printf("---");
if (x < row - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
接下来就是下棋了,只要玩家或者电脑出现横三竖三或者斜三相连的棋子就判定赢了。
在game里封装两个函数玩家和电脑移动,每次下棋打印出一次棋盘,反复的操作把这些步骤放在循环里。
void game()
{
char board[ROW][COL] = { 0 };
//初始化棋盘函数
Initboard(board, ROW, COL);
//打印棋盘
Displayboard(board, ROW, COL);
while (1)
{
//玩家移动
PlayerMove(board, ROW, COL);
Displayboard(board, ROW, COL);
//电脑移动
ComputerMove(board, ROW, COL);
Displayboard(board, ROW, COL);
}
}
玩家移动函数实现:
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
puts("玩家下棋");
while (1)
{
//虽然下标是从0开始
//但是用户并不知道二维数组
//要做出相应的调整
printf("请输入坐标:");
scanf("%d %d", &x, &y);
//坐标范围合法的判断
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//此时进行对应的坐标是否被占用
//注意调整,应该把X和Y的值-1才对应的下标
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
puts("坐标被占用,请选择其他位置\n");
}
}
else
{
puts("坐标非法,请重新输入\n");
}
}
}
接着就是电脑下棋,跟玩家下棋相似,代码实现:
//电脑移动
//简单点,让其在空白的地方随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
puts("电脑下棋>");
while (1)
{
//先让电脑随机生成下标
//范围在0~2
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
玩家和电脑可以正常下棋后,每次下棋都需要判断输赢,在game函数里封装isWin函数:
char ret = 0;
char board[ROW][COL] = { 0 };
puts("\n棋盘坐标有效范围(1~3)");
//初始化棋盘函数
Initboard(board, ROW, COL);
//打印棋盘
Displayboard(board, ROW, COL);
while (1)
{
//玩家移动
PlayerMove(board, ROW, COL);
//判断输赢
ret = isWin(board, ROW, COL);
Displayboard(board, ROW, COL);
//电脑移动
ComputerMove(board, ROW, COL);
//判断输赢
ret = isWin(board, ROW, COL);
Displayboard(board, ROW, COL);
}
isWin函数该干什么?如果玩家或者电脑赢了,又或者平局了都要提示一下,这几种情况都不是说明游戏继续。
自己可以规定一下:玩家赢了返回 * ,电脑赢了返回 # ,平局返回 Q,继续返回 C,因此此函数的返回类型是char.
除了继续,只要是其他三种情况就退出游戏。
while (1)
{
//玩家移动
PlayerMove(board, ROW, COL);
//判断输赢
ret = isWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
Displayboard(board, ROW, COL);
//电脑移动
ComputerMove(board, ROW, COL);
//判断输赢
ret = isWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
Displayboard(board, ROW, COL);
}
if (ret == '*')
{
puts("You win!");
}
else if (ret == '#')
{
puts("Computer win!");
}
else
{
puts("Draw");
}
这是上面几种情况的反应,接下就是函数的实现:
//判断输赢
char isWin(char board[ROW][COL], int row, int col)
{
//判断行
for (int i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
{
return board[i][0];
}
}
//判断列
for (int j = 0; j < row; j++)
{
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j] != ' ')
{
return board[0][j];
}
}
//判断对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
//到这里没人赢就要判断平局
//判断数组是否还有空格
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 'C';
}
}
//没有加就是平局了
return 'Q';
}
这种判断输赢的方法就写死了,固定3*3,待我后面再想想动态的写法吧,来运行一下代码:
最后运行程序没报错,逻辑也没问题。简易的三子棋就算是完成了。
game.h头文件函数声明:
#pragma once
#include
#include
#include
#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);
game.c函数定义:
#include "game.h"
//初始化棋盘
void Initboard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
//打印棋盘
void Displayboard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
//每打印一个元素就用|隔开
//|的数量应该是元素-1
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
//作为每行元素的分割
//打印次数也是元素-1次
if (i < row - 1)
{
for (int x = 0; x < row; x++)
{
printf("---");
if (x < row - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
//玩家移动
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
puts("玩家下棋>");
while (1)
{
//虽然下标是从0开始
//但是用户并不知道二维数组
//要做出相应的调整
printf("请输入坐标:");
scanf("%d %d", &x, &y);
//坐标范围合法的判断
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//此时进行对应的坐标是否被占用
//注意调整,应该把X和Y的值-1才对应的下标
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
puts("坐标被占用,请选择其他位置\n");
}
}
else
{
puts("坐标非法,请重新输入\n");
break;
}
}
}
//电脑移动
//简单点,让其在空白的地方随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
puts("电脑下棋>");
while (1)
{
//先让电脑随机生成下标
//范围在0~2
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
//判断输赢
char isWin(char board[ROW][COL], int row, int col)
{
//判断行
for (int i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
{
return board[i][0];
}
}
//判断列
for (int j = 0; j < row; j++)
{
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j] != ' ')
{
return board[0][j];
}
}
//判断对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
//到这里没人赢就要判断平局
//判断数组是否还有空格
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 'C';
}
}
//没有就是平局了
return 'Q';
}
test.c游戏主逻辑:
#include "game.h"
void menu()
{
puts("****************************");
puts("****** 1.play ******");
puts("****** 0.quit ******");
puts("****************************");
}
void game()
{
char ret = 0;
char board[ROW][COL] = { 0 };
puts("\n棋盘坐标有效范围(1~3)");
//初始化棋盘函数
Initboard(board, ROW, COL);
//打印棋盘
Displayboard(board, ROW, COL);
while (1)
{
//玩家移动
PlayerMove(board, ROW, COL);
//判断输赢
ret = isWin(board, ROW, COL);
Displayboard(board, ROW, COL);
if (ret != 'C')
{
break;
}
//电脑移动
ComputerMove(board, ROW, COL);
//判断输赢
ret = isWin(board, ROW, COL);
Displayboard(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
puts("You win!\n");
}
else if (ret == '#')
{
puts("Computer win!\n");
}
else
{
puts("Draw!\n");
}
}
int main()
{
int input = 0;
//设置随机数的起点生成
srand((unsigned)time(NULL));
char arr[ROW][COL] = { 0 };
do
{
menu();
printf("请输入选项:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
puts("退出游戏\n");
break;
default:
puts("输入错误!\n");
break;
}
} while (input);
return 0;
}
以上就是三子棋实现的代码,还有好多可以优化的地方,待我慢慢改善。