飞道的博客

【项目展示】自己用C语言编写的五子棋小程序

214人阅读  评论(0)

1、前言

2015年,我刚刚上大一。大一上学期,我们就学习了C语言这门课程。学了大概两个多月吧,我就心血来潮,在学校图书馆的机房里自主编写了我的第一个C语言项目——五子棋小程序。
我依稀记得,那个夏日的夜晚,风带着些许暑气,一个少年背着沉重的书包,拖着疲惫却又轻快的身躯,满意地走出图书馆的身影。他的心中有说不尽的甜——毕竟,做成了此生第一个完全由自己开发的小游戏。
而当时的我编写的五子棋小游戏是什么样子的呢?当时的我还不会GUI编程(虽然到现在也还没学会……),所以就借鉴了一下更早以前玩过的一个基于CUI的围棋小程序。那个围棋小程序,是用点“·”来表示棋盘上的每一个交叉点,用加号“+”来表示九个星位,用字母x来表示黑棋,用字母o来表示白棋。所以我的第一个五子棋小程序,也是这么做的。
用一个二维数组来储存当前棋盘的状态,然后用switch-case结构与双重for循环把它显示成对应的字符。接着,提示用户输入纵坐标与横坐标,在落子前还要先判断一下用户输入的坐标值是否超出范围。然后,再用原始又冗长的一长段判断语句,来判断有没有连成五子。虽然现在回看起来,那个判断条件真的是蠢萌蠢萌的,但当时的我真的是较劲了脑汁、使出了浑身解数,才想到了相对当时来说最高效的算法。
总的来说吧,那段时间还是挺快乐的,因为过得很充实,做这个做那个的,学到了很多,也创造了很多。但是呢,这个程序也暴露了当时的我编程的很多问题。比如,代码冗长,不懂得封装(因为还没有学到函数);比如,(现在看来)不必要的状态指示变量太多了,结构过于复杂。
基于以上种种问题,我决心把我的这个五子棋小程序从头再写一遍。利用后来所学的知识,该优化规格的优化规格,该优化代码的优化代码,该优化算法的优化算法。于是就做成了这篇文章所介绍的,我的新版本(2021版)五子棋小程序。

2、项目规格

【外部规格】
界面表现形式:CUI
操作方法:通过WSAD键控制光标上下左右移动,按空格键落子。
对战形式:仅可进行双人单机对战。无法进行人机对战、联网对战模式。
异常处理:当要落子的坐标上已经有棋子时,会报错提醒玩家重新落子。
回合制:否。该版本暂时为一回合游戏。胜负分晓即结束游戏。
BUG情况:最新版本中暂未发现BUG

【内部规格】
开发所用语言:C语言
代码总行数:195行
函数总数:6个(包括main函数)

3、核心算法

本程序用以检验是否连成五子的算法为,当棋手按下空格键落下棋子后即判定。以棋手落子位置为原点,首先进行x轴方向的判定:判断其本身与左侧4坐标点、右侧4坐标点,合计9个坐标点的范围内是否存在连续的五个子。具体的方法是,定义一个stoneCounter变量(初始值设作0),用以计数目标棋子在指定范围内连续出现了多少次。用for循环遍历这9个位置,每遇到一个目标棋子,stoneCounter++。但凡遇到一个非目标棋子的点,stoneCounter就立刻清零,从下一次再遇到目标棋子时开始重新计数。如果stoneCounter的值一旦达到了5,则立刻终止循环(注意:检测到五子连珠后立即终止循环很重要,否则容易出现BUG,导致明明已经五子了却判定为没有赢。因为,如果不停下来,而五子后面还有待检测的坐标,与目标棋子的代码不一致的话,stoneCounter就会清零,显示没有找到连续的五个子),返回肯定的判定结果给main函数,主程序就知道这名棋手胜利了。而如果遍历完9个位置,stoneCounter却始终没有到达过5,则说明检测范围内没有出现五子连珠。以此类推,y轴方向、函数y=x图像所在直线方向、函数y=-x图像所在直线方向亦然。

4、源代码

#include <stdio.h>
#include <stdlib.h>	//要调用系统命令,需要导入stdlib.h头文件
#include <conio.h>	//要接收键盘事件,需要导入conio.h头文件

#define X 1
#define O 2

char toSymbol(int num){
   		//函数作用:给定一个棋盘位置的状态值,返回这个状态值所对应的图形符号
	switch(num){
   		//其实本来是想用实心圆●表示黑棋,用空心圆⭕表示白棋的,但这次下的这个编译器似乎不太允许这样,显示出来全是乱码,于是只好改用X和O。
		case 0:
			return '.';		//空交叉点
		case X:
			return 'X';		//棋子X
		case O:
			return 'O';		//棋子O
		case 9:
			return '+';		//星位
	}
}

int check_hor(int panel[][15], int y, int x, int object){
   	//检查x轴方向上是否连成五子
	int stoneCount = 0;
	for(int i = x-4; i<=x+4; i++){
   
		if(panel[y][i]==object){
   
			stoneCount++;
			if(stoneCount == 5){
   
				break;
			}
		}else{
   
			stoneCount = 0;
		}
	}
	if(stoneCount >= 5){
   
		return 1;
	}else{
   
		return 0;
	}
}

int check_ver(int panel[][15], int y, int x, int object){
   	//检查y轴方向上是否连成五子
	int stoneCount = 0;
	for(int i = y-4; i<=y+4; i++){
   
		if(panel[i][x]==object){
   
			stoneCount++;
			if(stoneCount==5){
   
				break;
			}
		}else{
   
			stoneCount = 0;
		}
	}
	if(stoneCount >= 5){
   
		return 1;
	}else{
   
		return 0;
	}
}

int check_slash(int panel[][15], int y, int x, int object){
   	//检查函数y=-x的图像所在直线方向上是否连成五子
	int stoneCount = 0;
	for(int i = y-4,j=x-4; i<= y+4; i++,j++){
   
		if(panel[i][j]==object){
   
			stoneCount++;
			if(stoneCount==5){
   
				break;
			}
		}else{
   
			stoneCount = 0;
		}
	}
	if(stoneCount >= 5){
   
		return 1;
	}else{
   
		return 0;
	}
}

int check_backslash(int panel[][15], int y, int x, int object){
   	//检查函数y=x的图像所在直线方向上是否连成五子
	int stoneCount = 0;
	for(int i = y+4, j=x-4; i>=y-4; i--,j++){
   
		if(panel[i][j]==object){
   
			stoneCount++;
			if(stoneCount==5){
   
				break;
			}
		}else{
   
			stoneCount = 0;
		}
	}
	if(stoneCount >= 5){
   
		return 1;
	}else{
   
		return 0;
	}
}

int main(void){
   
	int key = 0;
	
	//棋盘初始状态
	int panel[15][15] =
	{
   {
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,9,0,0,0,0,0,0,0,9,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,9,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,9,0,0,0,0,0,0,0,9,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
	
	int cus[]={
   7,7};	//光标初始位置
	int turn = X;		//初始玩家
	int winner=0;		//赢家:默认暂时没有
	
	while(1){
   
		//清屏,并显示现在是谁走棋
		system("cls");
		printf("现在是【%c】方走棋……\n",toSymbol(turn));
		
		//显示棋盘
		for(int i = 0; i < 15; i++){
   
			for(int j=0; j < 15; j++){
   
				if(i==cus[0] && j==cus[1]){
   
					printf("[ %c ]", toSymbol(panel[i][j]));
				}else{
   
					printf("  %c  ", toSymbol(panel[i][j]));
				}
			}//next j
			printf("\n\n");
		}//next i
		
		//如果有人赢了,显示赢家是谁,并结束程序
		if(winner!=0){
   
			printf("五子连珠!玩家【%c】胜!\n", toSymbol(winner));
			return 0;
		}
	
		//接收键盘事件
		key = getch();
		
		//按WASD进行控制,空格键落子,L键结束游戏
		switch(key){
   
			case 'w': case 'W':
				if(cus[0]==0) cus[0]=14;
				else cus[0]--;
				break;
			case 'a': case 'A':
				if(cus[1]==0) cus[1]=14;
				else cus[1]--;
				break;
			case 's': case 'S':
				if(cus[0]==14) cus[0]=0;
				else cus[0]++;
				break;
			case 'd': case 'D':
				if(cus[1]==14) cus[1]=0;
				else cus[1]++;
				break;
			case 32:
				if(panel[cus[0]][cus[1]] != X && panel[cus[0]][cus[1]] != O){
   
					panel[cus[0]][cus[1]] = turn;
					if(check_hor(panel,cus[0],cus[1],turn)==1
					|| check_ver(panel,cus[0],cus[1],turn)==1
					|| check_slash(panel,cus[0],cus[1],turn)==1
					|| check_backslash(panel, cus[0],cus[1],turn)==1){
   
						winner = turn;
						break;
					}
					if(turn == X) turn = O;
					else turn = X;
				}else{
   
					printf("这里已经有子了,重来!\n");
					system("pause");
				}
				break;
			case 'l': case 'L':
				printf("结束程序……\n");
				system("pause");
				return 0;
			default:
				printf("无效按键!\n");
				system("pause");
				break;
		}
	}
	return 0;
}

5、运行结果

一开始的时候出了几个小BUG,比如光标无法正确移动啦,无法正确判定是否连成五子什么的。但后来,这些问题也都在我的刻苦钻研下迎刃而解了。目前看来结果还算是正常的,没有再发现什么其他BUG了。不过如果有热心的同志运行我的代码发现了新的BUG,或者有什么意见或建议的(特别是在算法优化方面),欢迎评论留言私信。

6、未来计划

未来还计划制作基于CUI的象棋小程序、汉诺塔小程序。并且再往后还计划走出CUI,使用Java将他们GUI化,做出真正的图形界面游戏。如果你还有什么新颖的创意,也欢迎联系我。
路漫漫其修远兮,吾将上下而求索。


转载:https://blog.csdn.net/weixin_41018509/article/details/115557449
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场