文章目录
前言一、问题描述二、基本流程三、步骤1.构建程序主体框架以及菜单的实现2.良好的宏定义增强代码可读性3.构建游戏入口PlayersGame()函数4.实现打印棋盘函数5.实现玩家落子函数6.实现判断输赢函数 四、结果演示五、具体代码总结前言
初学C语言,了解基本语法后,可以用来练练手。一、问题描述
用C语言实现玩家对战的五子棋。
二、基本流程
在具体写代码之前,先来确定要实现的功能和实现流程。
创建菜单界面并选择开始或者退出游戏——菜单函数。若选择开始则打印棋盘——打印棋盘的函数。玩家1,玩家2,分别开始落子——落子函数。每当玩家落子就需要判断输赢——判断是否结束的函数。若结束则再次打印棋盘,并输出结果。否则返回第二步开始循环。
三、步骤
为了让代码和逻辑更清晰,方便修改,调试。我们采用多文件协作,可创建三个文件,gobang.h用来存放头文件,函数以及宏定义的声明;gobang.c用于存放函数的定义;gobang_test.c用来测试程序。分别在gobang.c和gobang_test.c中引入头文件gobang.h即可。
1.构建程序主体框架以及菜单的实现
此步骤放在gobang_test.c中,是程序的开始。
包括菜单的实现,以及使用一个do while循环控制是否退出游戏,其中用switch语句实现用户的选择。
#include"gobang.h"//游戏菜单void Menu(){printf("****************************************\n");printf("****1.玩家PK ****\n");printf("****0.退出****\n");printf("****************************************\n");}int main(){int choice;do{Menu();printf("请选择:>");scanf("%d", &choice);switch (choice){case 1:PlayersGame();break;case 0:break;default:printf("输入错误,请重新选择:>");break;}} while (choice);return 0;}
2.良好的宏定义增强代码可读性
宏定义可以让代码的可读性更好,也可以让我们写代码时减少出错。
#define ROW 20 //数组行数,可以按照需求调整#define COL 20 //数组列数,可以按照需求调整#define PLAYER1 1//玩家1编号,默认棋盘数据是0,玩家1落子,该位置被改成1#define PLAYER2 2//玩家2编号,默认棋盘数据是0,玩家2落子,该位置被改成2//落子之后的四种情况#define NEXT 0 //游戏继续#define PLAYER1_WIN 1 //玩家1赢了#define PLAYER2_WIN 2 //玩家2赢了#define DRAW 3 //平局
这些定义后面都会用到。
3.构建游戏入口PlayersGame()函数
这是主要的五子棋框架,将会调用具体函数来完成。
可以将需要的函数先写出来,再具体去实现。
这里需要落一次子,就判断一次。
//玩家对战void PlayersGame(){int board[ROW][COL];//创建一个二维数组作为棋盘memset(board, 0, sizeof(board));//将数组每个元素赋值为零int row = ROW;intcol = COL;int ret = NEXT;//获取每落一个子的结果while (1){ShowBoard(board, row, col);PlayerMove(board, row, col, PLAYER1);ret = IsOver(board, row, col); //判断是否结束//如果不是继续就退出if (ret != NEXT)break;//玩家2ShowBoard(board, row, col);PlayerMove(board, row, col, PLAYER2);ret = IsOver(board, row, col); //判断是否结束//如果不是继续就退出if (ret != NEXT)break;}ShowBoard(board, row, col);switch (ret){case PLAYER1_WIN:printf("恭喜玩家1获胜\n");break;case PLAYER2_WIN:printf("恭喜玩家1获胜\n");break;case DRAW:printf("平局,再战一次?\n");break;default:break;}}
4.实现打印棋盘函数
为了更加真实的模拟棋盘,我们可以选取一些特殊符号。
默认符号比较丑陋,我们需要使用美化后的符号,下面链接里面可找到所需的黑白棋,复制粘贴即可。
特殊符号大全.
打印的时候,要根据元素的内容分别打印对印的符号。
我这里的列和行都是以0开始。
//打印棋盘void ShowBoard(int board[][COL], int row, int col){system("cls");printf(" ");for (int i = 0; i < col; i++){printf("%-3d", i);//打印列号}printf("\n");for (int i = 0; i < row; i++){printf("%2d", i);//打印行号for (int j = 0; j < col; j++){if (board[i][j] == PLAYER1){//玩家1落子printf(" ●");}else{if (board[i][j] == PLAYER2)printf(" ○"); //玩家2落子elseprintf(" ·"); //没有玩家落子}}printf("\n");}printf("\n");}
5.实现玩家落子函数
要实现玩家落子,可以用两个整形变量来获取坐标,但是后面判断输赢函数我们也会用到输入的坐标,所以需要设置成全局变量。
//因为判定结果时需要用到玩家落子的坐标,所有设置两个全局变量方便使用int x, y;//x是行,y是列
参数player是用来记录当前是哪个玩家落子。
//玩家下棋void PlayerMove(int board[][COL], int row, int col , int player){printf("请玩家%d输入落子的坐标:>",player);while (1){scanf("%d%d", &x, &y);if (x<0 || x>=row || y<0 || y>=col){printf("输入坐标错误,请重新输入:>");}else{if (board[x][y] != 0){printf("此坐标已有子,请重新输入:>");}else{board[x][y] = player;//将该点改为落子的玩家的值break;}}}}
6.实现判断输赢函数
这是实现五子棋的难点。
任何落子位置都有八个方向,所以判定五子连珠,本质是判定1,5方向之和,2,6方向之和,3,7方向之和,4,8方向之和,其中任意一个出现相同的连续五个棋子,即游戏结束
为了记录这八个方向我们采用枚举来记录,增加代码的可读性
// 枚举八个方向//左右,上下,左上,左下,右上,右下enum Dir {LEFT,RIGHT,UP,DOWN,LEFT_UP,RIGHT_DOWN,RIGHT_UP,LEFT_DOWN};
这里就需要再创建一个用来记录相连棋子数量的函数。
参数enum dir d是传入的方向。
我们采用while循环和switch语句,用来循环笔记该方向上相同棋子的数量。
我们可以参照上图,对不同的方向,坐标做相应的加减。
//棋子计数int ChessCount(int board[][COL], int row, int col, enum dir d){int count = 0;int _x = x;int _y = y;while (1){switch (d){case UP://上方_x--;break;case DOWN:_x++;break;case LEFT:_y--;break;case RIGHT:_y++;break;case LEFT_UP:_x--; _y--;break;case LEFT_DOWN:_x++; _y--;break;case RIGHT_UP:_x--; _y++;break;case RIGHT_DOWN:_x++; _y++;break;default:break;}//移动坐标之后,保证是否越界if (x < 0 || x >= row || y < 0 || y >= col){break;}//判断是否和原棋子相同,相同则计数if (board[_x][_y] == board[x][y]){count++;}else{break;}}return count;}
四、结果演示
这里使用了清屏函数system(“cls”)使得界面更加整洁。
五、具体代码
gobang.h
#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>#include<stdlib.h>#include<time.h>#include<string.h>#define ROW 20 //数组行数,可以按照需求调整#define COL 20 //数组列数,可以按照需求调整#define PLAYER1 1//玩家1编号,默认棋盘数据是0,玩家1落子,该位置被改成1#define PLAYER2 2//玩家2编号,默认棋盘数据是0,玩家2落子,该位置被改成2//落子之后的四种情况#define NEXT 0 //游戏继续#define PLAYER1_WIN 1 //玩家1赢了#define PLAYER2_WIN 2 //玩家2赢了#define DRAW 3 //平局//因为判定结果时需要用到玩家落子的坐标,所有设置两个全局变量方便使用int x, y;//x是行,y是列// 枚举八个方向//左右,上下,左上,左下,右上,右下enum Dir {LEFT,RIGHT,UP,DOWN,LEFT_UP,RIGHT_DOWN,RIGHT_UP,LEFT_DOWN};//玩家对战void PlayersGame();//打印棋盘void ShowBoard(int board[][COL], int row, int col);//玩家下棋void PlayerMove(int board[][COL], int row, int col, int player);//判断是否结束int IsOver(int board[][COL], int row, int col);//棋子计数int ChessCount(int board[][COL], int row, int col, enum dir d);//判断棋盘是否满//满了返回1int BoardIsFull(int board[][COL], int row, int col);
gobang.c
#include"gobang.h"//玩家对战void PlayersGame(){int board[ROW][COL];//创建一个二维数组作为棋盘memset(board, 0, sizeof(board));//将数组每个元素赋值为零int row = ROW;intcol = COL;int ret = NEXT;//获取每落一个子的结果while (1){ShowBoard(board, row, col);PlayerMove(board, row, col, PLAYER1);ret = IsOver(board, row, col); //判断是否结束//如果不是继续就退出if (ret != NEXT)break;//玩家2ShowBoard(board, row, col);PlayerMove(board, row, col, PLAYER2);ret = IsOver(board, row, col); //判断是否结束//如果不是继续就退出if (ret != NEXT)break;}ShowBoard(board, row, col);switch (ret){case PLAYER1_WIN:printf("恭喜玩家1获胜\n");break;case PLAYER2_WIN:printf("恭喜玩家1获胜\n");break;case DRAW:printf("平局,再战一次?\n");break;default:break;}}//打印棋盘void ShowBoard(int board[][COL], int row, int col){system("cls");printf(" ");for (int i = 0; i < col; i++){printf("%-3d", i);//打印列号}printf("\n");for (int i = 0; i < row; i++){printf("%2d", i);//打印行号for (int j = 0; j < col; j++){if (board[i][j] == PLAYER1){//玩家1落子printf(" ●");}else{if (board[i][j] == PLAYER2)printf(" ○"); //玩家2落子elseprintf(" ·"); //没有玩家落子}}printf("\n");}printf("\n");}//玩家下棋void PlayerMove(int board[][COL], int row, int col , int player){printf("请玩家%d输入落子的坐标:>",player);while (1){scanf("%d%d", &x, &y);if (x<0 || x>=row || y<0 || y>=col){printf("输入坐标错误,请重新输入:>");}else{if (board[x][y] != 0){printf("此坐标已有子,请重新输入:>");}else{board[x][y] = player;//将该点改为落子的玩家的值break;}}}}//判断是否结束int IsOver(int board[][COL], int row, int col){//分别统计四条线上的棋子数量int count1 = ChessCount(board, row, col, LEFT) + ChessCount(board, row, col, RIGHT) + 1;int count2 = ChessCount(board, row, col, UP) + ChessCount(board, row, col, DOWN) + 1;int count3 = ChessCount(board, row, col, LEFT_UP) + ChessCount(board, row, col, RIGHT_DOWN) + 1;int count4 = ChessCount(board, row, col, LEFT_DOWN) + ChessCount(board, row, col, RIGHT_UP) + 1;if (count1 >= 5 || count2 >= 5 || count3 >= 5 || count4 >= 5){if (board[x][y] == PLAYER1)return PLAYER1_WIN;elsereturn PLAYER2_WIN;}//如果没有五子联珠,判断棋盘是否满了if (BoardIsFull(board, row, col))return DRAW;//满了就平局elsereturn NEXT;//没满就继续}//棋子计数int ChessCount(int board[][COL], int row, int col, enum dir d){int count = 0;int _x = x;int _y = y;while (1){switch (d){case UP://上方_x--;break;case DOWN:_x++;break;case LEFT:_y--;break;case RIGHT:_y++;break;case LEFT_UP:_x--; _y--;break;case LEFT_DOWN:_x++; _y--;break;case RIGHT_UP:_x--; _y++;break;case RIGHT_DOWN:_x++; _y++;break;default:break;}//移动坐标之后,保证是否越界if (x < 0 || x >= row || y < 0 || y >= col){break;}//判断是否和原棋子相同,相同则计数if (board[_x][_y] == board[x][y]){count++;}else{break;}}return count;}//判断棋盘是否满//满了返回1int BoardIsFull(int board[][COL], int row, int col){for (int i = 0; i < row; i++){for (int j = 0; j < col; j++){if (board[i][j] == 0)return 0;}}return 1;}
gobang_test.c
#define _CRT_SECURE_NO_WARNINGS 1#include"gobang.h"//游戏菜单void Menu(){printf("****************************************\n");printf("****1.玩家PK ****\n");printf("****0.退出****\n");printf("****************************************\n");}int main(){int choice;do{Menu();printf("请选择:>");scanf("%d", &choice);switch (choice){case 1:PlayersGame();break;case 0:break;default:printf("输入错误,请重新选择:>");break;}} while (choice);return 0;}