C - 实现N子棋游戏
测了玩家和电脑连成行、列、两个对角线、平局的结果都没问题。
修改行列可做到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++){ // 一行结束,重新置0playerCount = 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,同时也将列判断无效的结果置0playerCount = 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++){ // 一行结束,重新置0playerCount = 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,同时也将列判断无效的结果置0playerCount = 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';
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
