当前位置:主页 > 查看内容

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

发布时间:2021-06-07 00:00| 位朋友查看

简介:本文目录 1、前言 2、项目规格 3、核心算法 4、源代码 5、运行结果 6、未来计划 1、前言 2015年我刚刚上大一。大一上学期我们就学习了C语言这门课程。学了大概两个多月吧我就心血来潮在学校图书馆的机房里自主编写了我的第一个C语言项目——五子棋小程序。……

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
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐