测了玩家和电脑连成行、列、两个对角线、平局的结果都没问题。
修改行列可做到N字棋,根据行列动态判断行、列、两边对角线的输赢条件。
虽然只是一个小游戏,但还是有几百行代码的,而且为了 看起来有条理,这里分成了三个文件。
1、test.c:main函数、其它主要的函数调用。
2、game.h:所有头文件的引入;常量定义、函数声明都统一在这里,test.c文件包含该头文件即可。
3、game.c:game.h中声明的函数在这里实现。
1、*是玩家的棋子;
2、#是电脑的棋子;
3、棋盘由二维字符数组实现,初始时全部元素为空字符;
4、为了看起来轻松,使用了一些符号 - | 对行、列进行了分隔。
这个比较简单,就是上来就先打印一个简易的菜单,这个比较直观就不放效果图了。
static void menu()
{
printf("-------------------------------------\n");
printf("-------------1.开始游戏--------------\n");
printf("-------------------------------------\n");
printf("-------------0.退出游戏-------------\n");
printf("-------------------------------------\n");
printf("-------------9.清理屏幕-------------\n");
printf("-------------------------------------\n");
}
int main()
{
int in;
do
{
menu();
scanf("%d", &in);
switch (in)
{
case 0:
printf("Notification:退出游戏.\n");
break;
case 1:
printf("Notification:开始游戏.\n");
game(); // 最主要的函数
break;
case 9:
printf("Notification:清理屏幕.\n");
system("cls");
break;
default:
printf("Notification:×没有该选项.\n");
break;
}
} while (in); // 0才退出,其它选项继续
return 0;
}
玩家输入选择,switch处理对应逻辑,输入值顺便还可以作为循环结束的条件。
作为棋盘类游戏,那么首先想到的肯定是使用二维数组作为棋盘,棋盘大小使用常量控制。
#define ROW 3
#define COL 3
static void game()
{
char board[ROW][COL];
initBoard(board);
displayBoard(board);
}
有了棋盘,那么再就是理所应当地对其初始化,前面说过初始化为空字符。
void initBoard(char board[ROW][COL])
{
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
board[i][j] = ' ';
}
}
}
初始化完之后再把棋盘打印,不过由于都是空字符,所以同时为了看起来轻松
或美观,用了一些字符进行了行、列的分隔,这样看得出来棋盘内坐标。
void displayBoard(char board[ROW][COL])
{
int i, j;
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");
}
}
棋盘分隔代码,还是有一些名堂在里面。
如最后每行最后一列不需要 | ,后面已经没有东西分隔了;
再就是最后一行不需要 — 进行分隔了,因为已经是最后一行。
不过怎么分割棋盘,分隔的方式对代码逻辑影响很大,到底该怎么打印还是要看怎么分隔。
棋盘分隔后效果如图:
如果不进行分隔,那么打印出来的就是空白嘛,看也看不见棋盘,
玩的时候还要记住之前落子的下标,以及对方落子的下标,这个难度就很大了。
下子不是下一个子游戏就完了,所以要用循环,至于什么时候结束循环后面再去写相应逻辑判断。
static void game()
{
char board[ROW][COL];
initBoard(board);
displayBoard(board);
while (1)
{
playerMove(board);
}
}
首先玩家需要输入x,y坐标。要注意的是坐标的合法性,
还有玩家不是程序员,所以这里的坐标都是从1开始,后续使用时再-1即可。
void playerMove(char board[ROW][COL])
{
int x, y;
while (1)
{
printf("Notification:玩家输入x、y坐标下棋子 > ");
scanf("%d %d", &x, &y);
// 玩家不知道从0开始
if (x >= 1 && x <= ROW
&& y >= 1 && y <= COL
&& board[--x][--y] == ' ') // 同时是空的才能落子,-1符合我们的逻辑
{
// 所以要-1再放入棋子(判断部分已经--)
board[x][y] = '*';
displayBoard(board); // 落子后显示棋盘
break;
}
else // 提示继续输入正确的坐标
{
printf("Notification:×坐标错误,请重新输入合适的坐标。\n");
printf("错误原因:可能是该坐标不在范围内,或已经有棋子落下。\n");
displayBoard(board);
}
}
}
static void game()
{
char board[ROW][COL];
initBoard(board);
displayBoard(board);
while (1)
{
playerMove(board);
computerMove(board);
}
}
大家很容易想到的是生成随机数作为下标,同时这样也是最容易实现的。
不过在C语言中使用随机数函数,还是有一些名堂在里面的。
void computerMove(char board[ROW][COL])
{
printf("Notification:电脑下棋子 > ");
int x, y;
while (1)
{
x = rand() % ROW; // 取余生成0到ROW-1的x下标,和0到COL-1的y坐标
y = rand() % COL;
if (board[x][y] == ' ') // 同样需要改坐标为空才能落子,否则继续生成新坐标
{
// +1正常显示给玩家看,玩家是不知道从0开始的。
printf("%d %d\n", x + 1, y + 1);
board[x][y] = '#';
displayBoard(board);
break;
}
}
}
rand()即生成随机数的函数,不过使用它有前提。
1、引入 stdlib.h 头文件;
2、在rand()使用前调用srand()函数初始化随机数,要注意的是该函数只需要执行一次,不要放在循环中,不然每次函数执行后,随机数会被固定产生固定的值。
3、往srand()函数传入一个动态的值,这里使用的是time()函数产生时间戳,时间戳无时不刻在变化,非常适合。time()函数中传空指针即可,同时将返回值强转成无符号整型以适合srand()形参类型。
int main()
{
// 只需要执行一次,不要放在循环中,不然每次函数执行后,随机数会被固定产生固定的值。
srand((unsigned int)time(NULL));
int in;
do
{
menu();
printf("Notification:请选择 > ");
scanf("%d", &in);
switch (in)
{
...
}
} while (in);
return 0;
}
每次落子后都要进行判断。
static void game()
{
char board[ROW][COL];
initBoard(board);
displayBoard(board);
while (1)
{
playerMove(board);
if (getResult(doesWin(board)) == 0)
{
break;
}
computerMove(board);
if (getResult(doesWin(board)) == 0)
{
break;
}
}
}
doesWin()
函数才是整个游戏的核心,根据棋盘大小进行了动态判断,不仅仅局限于三子棋。
/*
返回字符:
'*' - 玩家赢
'#' - 电脑赢
'0' - 平局
'1' - 继续游戏
*/
char doesWin(char board[ROW][COL])
{
int i, j;
// 玩家或电脑的棋子分别在棋盘上行、列、对角线的棋子个数
int playerCount = 0,
computerCount = 0;
// 1.行判断
for (i = 0; i < ROW; i++)
{ // 一行结束,重新置0
playerCount = 0;
computerCount = 0;
for (j = 0; j < COL; j++)
{
if (board[i][j] == '*')
{
playerCount++;
}
else if (board[i][j] == '#')
{
computerCount++;
}
}
// 虽然是行判断,但由该行上的列数棋子决定有没有赢
if (playerCount == COL || computerCount == COL)
{ // 上面最后一次循环完了后,j还进行了++。
return board[i][j - 1]; // 要么*要么#,无所谓。
}
}
// 2.列判断(循环条件、数组下标和以往不同思维)
for (i = 0; i < COL; i++)
{
// 既是对上面行判断留下的结果置0,同时也将列判断无效的结果置0
playerCount = 0;
computerCount = 0;
for (j = 0; j < ROW; j++)
{ // j当做行,i当做列(一趟循环固定不变)
if (board[j][i] == '*')
{
playerCount++;
}
else if (board[j][i] == '#')
{
computerCount++;
}
}
// 虽然是列判断,但由每行上的固定列数的棋子决定输赢
if (playerCount == ROW || computerCount == ROW)
{ // 上面循环最后i++了一下,要-1。
return board[j - 1][i];
}
}
// 3.从左向右下角的对角线判断 - [0][0] [1][1] [2][2]
playerCount = 0;
computerCount = 0;
for (i = 0; i < ROW; i++)
{
if (board[i][i] == '*')
{
playerCount++;
}
else if (board[i][i] == '#')
{
computerCount++;
}
}
if (playerCount == ROW || computerCount == ROW)
{
return board[0][0];
}
// 4.从右向左下角的对角线判断 - [0][2] [1][1] [2][0]
playerCount = 0;
computerCount = 0;
for (i = 0; i < ROW; i++)
{ // 这个对角线,只需要考虑列下标的变化
if (board[i][COL - i - 1] == '*')
{
playerCount++;
}
else if (board[i][COL - i - 1] == '#')
{
computerCount++;
}
}
if (playerCount == ROW || computerCount == ROW)
{
return board[0][COL - 1];
}
// 5.平局判断(前面任何一方没赢,后面棋盘上判断是否还有空)
int spaceCount = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
{
spaceCount++;
}
}
}
if (!spaceCount) // 棋盘内1个空都没有,平局
{
return '0';
}
// 6.都没赢,也没平局,那么游戏继续
return '1';
}
getResult()
函数只是对doesWin()函数的返回结果进行了处理。
int getResult(char sign)
{
if (sign == '1')
{
return 1;
}
else if (sign == '*')
{
printf("Notification:玩家赢。\n");
}
else if (sign == '#')
{
printf("Notification:电脑赢。\n");
}
else if (sign == '0') {
printf("Notification:平局。\n");
}
return 0;
}
很明显,无论是电脑玩家赢,还是平局,游戏都不再继续,返回0。
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
/*
test.c - 函数调用
*/
static void menu()
{
printf("-------------------------------------\n");
printf("-------------1.开始游戏--------------\n");
printf("-------------------------------------\n");
printf("-------------0.退出游戏-------------\n");
printf("-------------------------------------\n");
printf("-------------9.清理屏幕-------------\n");
printf("-------------------------------------\n");
}
int getResult(char sign)
{
if (sign == '1')
{
return 1;
}
else if (sign == '*')
{
printf("Notification:玩家赢。\n");
}
else if (sign == '#')
{
printf("Notification:电脑赢。\n");
}
else if (sign == '0') {
printf("Notification:平局。\n");
}
return 0;
}
static void game()
{
char board[ROW][COL];
initBoard(board);
displayBoard(board);
while (1)
{
playerMove(board);
if (getResult(doesWin(board)) == 0)
{
break;
}
computerMove(board);
if (getResult(doesWin(board)) == 0)
{
break;
}
}
}
int main()
{
srand((unsigned int)time(NULL));
printf("Notification:请选择 > ");
int in;
do
{
menu();
scanf("%d", &in);
switch (in)
{
case 0:
printf("Notification:退出游戏.\n");
break;
case 1:
printf("Notification:开始游戏.\n");
game();
break;
case 9:
printf("Notification:清理屏幕.\n");
system("cls");
break;
default:
printf("Notification:×没有该选项.\n");
break;
}
} while (in);
return 0;
}
#pragma once
/*
game.h - 头文件引入、常量、函数声明
*/
// 棋盘大小 - 行和列
#define ROW 3
#define COL 3
#include
#include
#include
#include
// 显示菜单
static void menu();
// 初始化棋盘
void initBoard(char board[ROW][COL]);
// 显示棋盘
void displayBoard(char board[ROW][COL]);
// 玩家落子
void playerMove(char board[ROW][COL]);
// 电脑落子
void computerMove(char board[ROW][COL]);
// 判断输赢
char doesWin(char board[ROW][COL]);
#define _CRT_SECURE_NO_WARNINGS 1
/*
game.c - 函数实现
*/
#include "game.h"
// 初始化棋盘
void initBoard(char board[ROW][COL])
{
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
board[i][j] = ' ';
}
}
}
// 显示棋盘
void displayBoard(char board[ROW][COL])
{
int i, j;
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");
}
}
// 玩家落子
void playerMove(char board[ROW][COL])
{
int x, y;
while (1)
{
printf("Notification:玩家输入x、y坐标下棋子 > ");
scanf("%d %d", &x, &y);
// 玩家不知道从0开始
if (x >= 1 && x <= ROW
&& y >= 1 && y <= COL
&& board[--x][--y] == ' ')
{
// 所以要-1再放入棋子(判断部分已经--)
board[x][y] = '*';
displayBoard(board);
break;
}
else
{
printf("Notification:×坐标错误,请重新输入合适的坐标。\n");
printf("错误原因:可能是该坐标不在范围内,或已经有棋子落下。\n");
displayBoard(board);
}
}
}
// 电脑落子
void computerMove(char board[ROW][COL])
{
printf("Notification:电脑下棋子 > ");
int x, y;
while (1)
{
x = rand() % ROW;
y = rand() % COL;
if (board[x][y] == ' ')
{
// +1正常显示,玩家是不知道从0开始的。
printf("%d %d\n", x + 1, y + 1);
board[x][y] = '#';
displayBoard(board);
break;
}
}
}
/*
返回字符:
'*' - 玩家赢
'#' - 电脑赢
'0' - 平局
'1' - 继续游戏
*/
// 判断输赢
char doesWin(char board[ROW][COL])
{
int i, j;
// 玩家或电脑的棋子分别在棋盘上行、列、对角线的棋子个数
int playerCount = 0,
computerCount = 0;
// 行判断
for (i = 0; i < ROW; i++)
{ // 一行结束,重新置0
playerCount = 0;
computerCount = 0;
for (j = 0; j < COL; j++)
{
if (board[i][j] == '*')
{
playerCount++;
}
else if (board[i][j] == '#')
{
computerCount++;
}
}
// 虽然是行判断,但由该行上的列数棋子决定有没有赢
if (playerCount == COL || computerCount == COL)
{ // 上面最后一次循环完了后,j还进行了++。
return board[i][j - 1]; // 要么*要么#,无所谓。
}
}
// 列判断(循环条件、数组下标和以往不同思维)
for (i = 0; i < COL; i++)
{
// 既是对上面行判断留下的结果置0,同时也将列判断无效的结果置0
playerCount = 0;
computerCount = 0;
for (j = 0; j < ROW; j++)
{ // j当做行,i当做列(一趟循环固定不变)
if (board[j][i] == '*')
{
playerCount++;
}
else if (board[j][i] == '#')
{
computerCount++;
}
}
// 虽然是列判断,但由每行上的固定列数的棋子决定输赢
if (playerCount == ROW || computerCount == ROW)
{ // 上面循环最后i++了一下,要-1。
return board[j - 1][i];
}
}
// 从左向右下角的对角线判断 - [0][0] [1][1] [2][2]
playerCount = 0;
computerCount = 0;
for (i = 0; i < ROW; i++)
{
if (board[i][i] == '*')
{
playerCount++;
}
else if (board[i][i] == '#')
{
computerCount++;
}
}
if (playerCount == ROW || computerCount == ROW)
{
return board[0][0];
}
// 从右向左下角的对角线判断 - [0][2] [1][1] [2][0]
playerCount = 0;
computerCount = 0;
for (i = 0; i < ROW; i++)
{
if (board[i][COL - i - 1] == '*')
{
playerCount++;
}
else if (board[i][COL - i - 1] == '#')
{
computerCount++;
}
}
if (playerCount == ROW || computerCount == ROW)
{
return board[0][COL - 1];
}
// 平局判断(前面任何一方没赢,后面棋盘上判断是否还有空)
int spaceCount = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
{
spaceCount++;
}
}
}
if (!spaceCount) // 棋盘内1个空都没有
{
return '0';
}
// 游戏继续
return '1';
}