目录
设计思路
三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏分为双方对战,双方依次在9宫格棋盘上摆放棋子,率先将自己的三个棋子走成一条线就视为胜利,而对方就算输了,但是三子棋在很多时候会出现和棋的局面。
因为编写的控制台程序,所以我们的棋盘应该在控制台显示,输入坐标 ,进行下棋指令。我们用X和O分别代表我们和电脑玩家棋子。用一个3*3二维数组当作实际的“棋盘”。
编程实现
编程结构
Common.h
用于调用我们代码要用的库。
#include<stdio.h>
#include<time.h>
#include<stdbool.h>
Game.h
用于声明我们自己定义的函数。
#include"common.h"
//初始化游戏
void InitGame();
//显示棋盘
void ShowChessBoard();
//玩家下棋
void PlayerMove();
//电脑下棋
void ComputerMove();
//判断结果
char CheckResult();
//开始游戏
void StartGame();
GameMain.c
用于放置三子棋的主函数
#include"Game.h"
int main()
{
int select = 1;
while(select)
{
printf("****************************\n");
printf("* 简 易 三 子 棋 *\n");
printf("****************************\n");
printf("* [1] Play [0] Quit *\n");
printf("****************************\n");
printf("请选择:>");
scanf("%d", &select);
if(select == 0)
break;
if(select != 1)
{
printf("输入的命令有误,请重新输入......\n");
continue;
}
//开始游戏
StartGame();
}
printf("Game Over, GoodBye!!!!!!!!\n");
return 0;
}
Game.c
用于放置我们对于三子棋游戏功能相关函数,各个功能实现。
#include"Game.h"
#define ROW 3
#define COL 3
static char board[ROW][COL];
void InitGame()
{
for(int i=0; i<ROW; ++i)
{
for(int j=0; j<COL; ++j)
{
board[i][j] = ' ';
}
}
}
void ShowChessBoard()
{
for(int i=0; i<ROW; ++i)
{
printf("| %c | %c | %c |\n", board[i][0],
board[i][1],
board[i][2]);
if(i != ROW-1)
printf(" --- --- ---\n");
}
}
void PlayerMove()
{
printf("请玩家落子:\n");
int row, col;
while(1)
{
printf("请输入棋子的坐标(row:col):>");
scanf("%d %d", &row, &col); //2 2
if(board[row-1][col-1] != ' ')
{
printf("输入的位置已被占用,请重新输入....\n");
continue;
}
board[row-1][col-1] = 'x';
break;
}
}
void ComputerMove()
{
int row, col;
srand(time(0));
while(1)
{
//随机产生位置
row = rand() % ROW;
col = rand() % COL;
if(board[row][col] == ' ')
{
board[row][col] = 'o';
break;
}
}
}
bool _IsFullChessBoard()
{
for(int i=0; i<ROW; ++i)
{
for(int j=0; j<COL; ++j)
{
if(board[i][j] == ' ')
return false;
}
}
return true;
}
//x:玩家赢
//o:电脑赢
//h:和棋
//c:继续
char CheckResult()
{
//检查行
for(int i=0; i<ROW; ++i)
{
if(board[i][0]!=' ' && board[i][0]==board[i][1] && board[i][0]==board[i][2])
return board[i][0];
}
//检查列
for(int j=0; j<COL; ++j)
{
if(board[0][j]!=' ' && board[0][j]==board[1][j] && board[0][j]==board[2][j])
return board[0][j];
}
//判断斜线
if(board[1][1]!=' ' && ((board[0][0]==board[1][1] && board[0][0]==board[2][2])
|| (board[0][2]==board[1][1] && board[0][2]==board[2][0])))
return board[1][1];
//是否和棋
if(_IsFullChessBoard())
return 'h';
//继续下棋
return 'c';
}
void StartGame()
{
//1 初始化游戏
InitGame();
char winner;
//2 进入游戏
while(1)
{
//3 显示棋盘
ShowChessBoard();
//4 玩家下棋
PlayerMove();
//5 判断结果
winner = CheckResult();
if(winner != 'c')
break;
//6 电脑下棋
ComputerMove();
//7 判断结果
winner = CheckResult();
if(winner != 'c')
break;
}
//8 宣布结果
if(winner == 'x')
printf("恭喜你,玩家胜利.\n");
else if(winner == 'o')
printf("很遗憾,电脑胜利.\n");
}
功能模块
1.棋盘
既然我们是三子棋游戏,所以我们首先在控制台打印一个空白的棋盘,来供我们后续在上面进行下棋。这两个模块负责我们棋盘的初始化。第一个模块负责在棋盘上的数组中输入空格(我们看到的棋盘刚开始并不是空的,只是用空格来填充,让我们看起来像空的);第二个模块负责在控制台上打印表格的线条以及数组中的空格。
void InitGame()
{
for(int i=0; i<ROW; ++i)
{
for(int j=0; j<COL; ++j)
{
board[i][j] = ' ';
}
}
}
void ShowChessBoard()
{
for(int i=0; i<ROW; ++i)
{
printf("| %c | %c | %c |\n", board[i][0],
board[i][1],
board[i][2]);
if(i != ROW-1)
printf(" --- --- ---\n");
}
}
2.玩家输入
游戏我们设计的是玩家先手下棋,此处玩家不能输入超过棋盘的位置,如果输入则会提示重新输入;除此之外我们还判断了玩家输入的位置是否之前已经被下过棋子。
void PlayerMove()
{
printf("请玩家落子:\n");
int row, col;
while(1)
{
printf("请输入棋子的坐标(row:col):>");
scanf("%d %d", &row, &col); //2 2
if ((row > ROW) || (row < 1) || (col > COL) || (col < 1))
{
printf("输入有误,请重新输入....\n");
continue;
}
if (board[row - 1][col - 1] != ' ')
{
printf("输入的位置已被占用,请重新输入....\n");
continue;
}
board[row - 1][col - 1] = 'x';
break;
}
}
3.电脑输入
我们电脑输入确实是不太聪明,因为设计的输入逻辑是随机种子,即用时间来当种子,生成一个随机数,因为生成的随机数有非常大的可能会超出我们的棋盘范围,所以我们用 “ % ”运算符来确保它生成的随机数在我们的合法范围内。用随机数下棋会有一个效率上的缺陷,就是当棋盘上空位置不是很多的时候,有可能生成一个下棋的坐标,结果是之前下过的位置,所以当棋盘上位置不多的时候有可能电脑会下棋的时间会变长。
void ComputerMove()
{
int row, col;
srand(time(0));
while(1)
{
//随机产生位置
row = rand() % ROW;
col = rand() % COL;
if(board[row][col] == ' ')
{
board[row][col] = 'o';
break;
}
}
}
4.判断是否棋盘已满
这里判断的方法比较简单,就是遍历整个数组,判断数组内还有没有单元的值为 “空格” ,如果有则棋盘没满,没有则棋盘满了。
bool _IsFullChessBoard()
{
for(int i=0; i<ROW; ++i)
{
for(int j=0; j<COL; ++j)
{
if(board[i][j] == ' ')
return false;
}
}
return true;
}
5.判断是否哪一方胜利
我们此处是检查数组中是否有哪一行或那一列再或者那一对角线为同一个字符(一定要屏蔽掉初始化数组时设置的空格)如果有则代表其中某一方胜利。在这个模块里我们调用了判断棋盘是否已满的模块来判断是否和棋,如果满了且经过前面的判断没有人胜利,则代表本局和棋。
可优化:
1.因为每一方都最少需要三子才能胜利,我们可以在第五步的时候在开始判断是否有哪一方胜利,提高了效率。
2.对于判断棋盘是否已满的模块,我们可以在第九步下完之后判断一次是否为和棋
char CheckResult()
{
//检查行
for(int i=0; i<ROW; ++i)
{
if(board[i][0]!=' ' && board[i][0]==board[i][1] && board[i][0]==board[i][2])
return board[i][0];
}
//检查列
for(int j=0; j<COL; ++j)
{
if(board[0][j]!=' ' && board[0][j]==board[1][j] && board[0][j]==board[2][j])
return board[0][j];
}
//判断斜线
if(board[1][1]!=' ' && ((board[0][0]==board[1][1] && board[0][0]==board[2][2])
|| (board[0][2]==board[1][1] && board[0][2]==board[2][0])))
return board[1][1];
//是否和棋
if(_IsFullChessBoard())
return 'h';
//继续下棋
return 'c';
}
//x:玩家赢
//o:电脑赢
//h:和棋
//c:继续
6.开始游戏功能
此处就是将我们之前的功能模块进行整合、调用。
void StartGame()
{
//1 初始化游戏
InitGame();
char winner;
//2 进入游戏
while(1)
{
//3 显示棋盘
ShowChessBoard();
//4 玩家下棋
PlayerMove();
//5 判断结果
winner = CheckResult();
if(winner != 'c')
break;
//6 电脑下棋
ComputerMove();
//7 判断结果
winner = CheckResult();
if(winner != 'c')
break;
}
//8 宣布结果
if(winner == 'x')
printf("恭喜你,玩家胜利.\n");
else if(winner == 'o')
printf("很遗憾,电脑胜利.\n");
}
细节处理
数组的大小我们可以直接在开头用宏定义,可以大大节约我们的时间,也可以保证正确性。
#define ROW 3
#define COL 3
我们还可以在最后加上一个清屏函数来保证界面的整洁
system("cls");