C语言程序设计报告
题目: 五子棋
班级: 电气Q1041班
人数: 3人
小组成员: 周啸天、万广富、黄山奇
指导老师: 桂超
时间: 2011.11.30
目录
第一章 课程设计的目的和要求... 3
1.1 课程设计的目的... 3
1.2 课程设计的要求... 3
1.3 课程设计的实验环境... 3
第二章 功能描述... 4
第三章 总体设计... 5
3.1 功能模块设计... 5
3.1.1 任务执行流程图... 5
3.1.2 下棋函数流程图... 6
3.2 数据结构设计... 7
3.2.1 定义结构体... 7
3.2.2 定义数组... 7
3.2.3 全局变量... 7
3.3 函数功能描述... 7
第四章 程序实现... 8
4.1源码分析... 8
4.2运行结果及界面介绍... 22
第五章 后记... 27
第一章 课程设计的目的和要求
1.1 课程设计的目的
1.加深对C语言数据类型,运算,语句结构及其程序设计的基本方法理解和掌握;
2.熟练掌握流程图的绘制、程序设计文档的书写;
3.通过编写一个完整的程序,一方面可以检查我们这学期的学习情况,为以后的学习打下坚实的基础;
4.熟悉C语言游戏编程,掌握五子棋游戏开发的基本原理,从而为以后的程序开发奠定基础。
1.2 课程设计的要求
1、编写程序代码,调试所写程序使其能够正确运行;
2、能进行基本的五子棋操作,有图形界面,能够用键盘操作;
3、能够实现悔棋、存档和读档等附加功能
1.3 课程设计的实验环境
该课程设计在设计与实验过程中需要在windows XP系统/windows 2000以上系统中进行,程序设计要求在visual C++6.0平台中进行,完成代码的编写、编译、调试、测试等工作。本游戏对计算机硬件和操作系统要求极低,所以在这里只是把自己的电脑硬件参数和系统参数列下:
硬件:Cpu:2.1GHZ,内存,2GB,硬盘:320GB,操作系统:windows xp
软件环境:安装VC++6.0
第二章 功能描述
本程序用C语言实现了五子棋游戏,能进行基本的五子棋操作。程序能实现界面的初始化功能、下棋功能、人机智能对战功能、胜负判断功能、悔棋功能、读档及存档功能,通过键盘操作控制下棋。
(1)显示欢迎界面。在游戏开始时出现一个欢迎的界面同时介绍了游戏的规则;
(2)初始化功能。程序初始化屏幕和棋盘,默认玩家先行。
(3)下棋操作。利用W、S、A、D及空格键实现下棋操作,在下棋过程中能随时按ESC键退出。
(4)人机智能对战功能。电脑根据玩家的下棋对棋盘进行智能分析,然后下棋,实现人机对弈。
(5)悔棋功能。玩家可以有三次悔棋机会。
(6)胜负判断功能。程序能对下棋的结果进行判断,分出胜负。并显示获胜方。
(7)读档、存档功能。游戏中途退出会提示是否存档,如果存档,则下次开始的时候会提示是否读档继续上次的游戏。
第三章 总体设计
3.1 功能模块设计
3.1.1 任务执行流程图
否 是
否
是
3.1.2 下棋函数流程图
是 否
否
是
否 是
否
否
是
3.2 数据结构设计
3.2.1 定义结构体
将棋盘上每个点的左边定义为一个结构体;
typedef struct
{
int x, y;
}point;
3.2.2 定义数组
定义数组board[15][15]表示棋盘,用来记录棋盘上每个棋子的状态;
3.2.3 全局变量
定义整形数组 back[4] 用来记录前两步双方下棋的状态,便于后面进行悔棋操作
定义整形 n = 3; 用来记录悔棋次数
3.3 函数功能描述
1、显示欢迎信息 bool welcome();
2、初始化棋盘 void InitBoard();
3、输出棋盘 void chessboard();
4、判断胜负 int Win(char c);
5、下棋 void play(point &r);
6、显示获胜 void showsusscced(char c);
7、悔棋 bool BackStep(int back[]);
8、人机对战智能算法 void ComAlgo(point &r);
9、存盘函数 bool SaveLoad();
10、读盘函数 bool DownLoad();
第四章 程序实现
4.1源码分析
1、显示欢迎信息
bool welcome()
{
char ch;
printf("\n\n\n\n");
printf("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n");
printf("┃ Welcome you to gobang World! ┃\n");
printf("┃1、You can use the A,D,W and S key to move the chessman; ┃\n");
printf("┃2、You can press Space key to enter after you move it; ┃\n");
printf("┃3、You can use Esc key to exit the game; ┃\n");
printf("┃4、Don't move the pieces out of the chessboard. ┃\n");
printf("┃ Do you want to continue?(Y/N) ┃\n");
printf("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n");
while (! strchr("YN", ch = toupper(getch())))
{
putchar('\a');
}
if(ch == 'N') return false;
else return true;
}
2、初始化棋盘
void InitBoard()
{
char ch; int i, j;
printf("\n\n是否读档?(Y/N)\n");
if((ch = toupper(getch())) == 'Y' && DownLoad())
printf("读档成功!\n");
else
{
for(i = 0; i < 15; i ++)
for(j = 0; j < 15; j ++)
board[i][j] = ' ';
}
chessboard();
}
3、输出棋盘
void chessboard()
{
//清屏
system("cls");
//输出棋盘的上边缘
printf(" 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 \n");
printf(" ┏━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┳━┓\n");
printf(" ┃ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┃\n");
for(int i = 1; i <= 15; i ++)
{
//输出列序号及相应的列元素
printf("%02d┣─", i);
for(int j = 1; j <= 15; j ++)
{
switch (board[i - 1][j - 1])
{
//(由于在命令行模式下显示,所以,颜色是颠倒的)
case ' ': printf("┼─"); break; //如果当前位置无子,则输出棋盘
case 'h': printf("○─"); break; //如果是黑子,则输出黑子的符号
case 'b': printf("●─"); break; //如果是白子,则输出白子的符号
case 'g': printf("⊙─"); break; //显示光标
}
}
//输出每列的最后一个制表符
printf("┫%02d\n", i);
printf(" ┃ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┃\n");
}
printf(" ┗━┷━┷━┷━┷━┷━┷━┷━┷━┷━┷━┷━┷━┷━┷━┷━┛ \n");
printf(" 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 \n");
}
4、下棋
void play(point &r)
{
char key, c;
do
{
while (! strchr("ADWSZ", key = toupper(getch())))
{
if(key == 27 || key == 32) break;
putchar('\a');
}
switch(key)
{
case 'A': //向左
if(r.y <= 1) break;
else r.y --;
c = board[r.x - 1][r.y - 1]; //记录光标
board[r.x - 1][r.y - 1] = 'g';
chessboard();
board[r.x - 1][r.y - 1] = c;
break;
case 'D': //向右
if(r.y >= 15) break;
else r.y ++;
c = board[r.x - 1][r.y - 1];
board[r.x - 1][r.y - 1] = 'g';
chessboard();
board[r.x - 1][r.y - 1] = c;
break;
case 'W': //向上
if(r.x <= 1) break;
else r.x --;
c = board[r.x - 1][r.y - 1];
board[r.x - 1][r.y - 1] = 'g';
chessboard();
board[r.x - 1][r.y - 1] = c;
break;
case 'S': //向下
if(r.x >= 15) break;
else r.x ++;
c = board[r.x - 1][r.y - 1];
board[r.x - 1][r.y - 1] = 'g';
chessboard();
board[r.x - 1][r.y - 1] = c;
break;
case 32: //SPACE空格
if(board[r.x - 1][r.y - 1] != ' ') key = 0; //key的值修改为非32的数值
else
{
board[r.x - 1][r.y - 1] = 'b';
back[0] = r.x - 1; back[1] = r.y - 1; //记录当前位置,便于悔棋
chessboard();
}
break;
case 'Z': //悔棋
BackStep(back);
break;
case 27: //ESC退出
printf("Game Over!\n");
printf("是否存档?(Y/N)\n");
if((c = toupper(getch())) == 'Y' && SaveLoad())
printf("存档成功!\n");
exit(1);
default:
fflush(stdin);
}
}while(key != 32);
}
注:
1、W、S、A、D分别表示上下左右键。如果超出了棋盘则不进行操作。否则向键值指示的方向移动一步。
2、c = board[r.x - 1][r.y - 1];
board[r.x - 1][r.y - 1] = 'g';
chessboard();
board[r.x - 1][r.y - 1] = c;
这四句表示记录当前移动位置,然后显示光标,显示完以后还原棋盘。
3、按空格键表示下棋,如果当前位置有棋子则不进行操作。back[0] = r.x - 1; back[1] = r.y - 1; 用于记录当前位置,便于悔棋 。
4、按Z悔棋
5、按ESC键退出,并且提示是否存档。
5、判断胜负
int Win(char c)
{
int i, j, ok = 1;
//判断横着的5个是否都相等
for(i = 0; i < 15; i ++)
{
for(j = 0; j < 11; j ++)
if(board[i][j]==c && board[i][j+1]==c && board[i][j+2]==c && board[i][j+3]==c && board[i][j+4]==c)
return ok;
}
//判断竖着的5个是否都相等
for(j = 0; j < 15; j ++)
{
for(i = 0; i < 11; i ++)
if(board[i][j]==c && board[i+1][j]==c && board[i+2][j]==c && board[i+3][j]==c && board[i+4][j]==c)
return 1;
}
//判断左斜5个是否都相等
for(i = 0; i < 11; i ++)
{
for(j = 0; j < 11; j ++)
if(board[i][j]==c && board[i+1][j+1]==c && board[i+2][j+2]==c && board[i+3][j+3]==c && board[i+4][j+4]==c)
return ok;
}
//判断右斜5个是否都相等
for(i = 0; i < 11; i ++)
{
for(j = 14; j > 3; j --)
if(board[i][j]==c && board[i+1][j-1]==c && board[i+2][j-2]==c && board[i+3][j-3]==c && board[i+4][j-4]==c)
return ok;
}
return ok = 0;
}
注:全篇扫面棋盘,从四个方向判断是否存在连着5个棋子
6、显示获胜
void showsusscced(char c)
{
if(c == 'b')
{
printf("游戏结束 白棋获胜!\n");
}
else if(c == 'h')
{
printf("游戏结束 黑棋获胜!\n");
}
else
{
printf("游戏结束 和棋!\n");
}
}
7、悔棋
bool BackStep(int back[])
{
if(n > 0)
{
board[back[0]][back[1]] = board[back[2]][back[3]] = ' ';
chessboard();
n --;
printf("悔棋成功,您还有%d次悔棋机会\n", n);
return true;
}
else
{
printf("悔棋超过三次,您不能悔棋了!\n");
return false;
}
}
8、存盘函数
bool SaveLoad()
{
FILE *f;
f = fopen("Load.TXT", "w");
if(f == NULL)
{
printf("存档失败!\n");
return false;
}
else
{
fwrite(board, sizeof(char), 225, f);
fclose(f);
return true;
}
}
9、读盘函数
bool DownLoad()
{
FILE *f;
f = fopen("Load.TXT", "r");
if(f == NULL)
{
printf("读档失败!\n");
return false;
}
else
{
fread(board, sizeof(char), 225, f);
fclose(f);
return true;
}
}
注:存盘存在当前文件夹内的Load.txt文件中,用fwrite()函数和fread()函数读取和存储数组元素。
10、人机对战智能算法
void ComAlgo(point &r)
{
//棋型数组
int qiju[2][15][15][8][2] = {0};
/*其中第一个下标为0时表示白棋,为1时表示黑棋,第二和第三个下标表示(x,y),
第四个下标表示8个方向,最后一个下标为0时表示棋子数,为1时表示空格数*/
char d; //表示黑棋或白棋
int k, i, j, q, a = 1; //(k, i, j, q, 0/1)表示棋型数组; a, b用来计数
int b = 0, y1 = 0, y2 = 0, x1 = 0, x2 = 0; // b表示得分; (x1, y1)和(x2, y2)表示坐标
int a1[15][15] = {0}, a2[15][15] = {0}; //用来记录白棋和黑棋各个棋子位置的得分
/****************为双方填写棋型表************/
for(k = 0; k < 2; k ++)
for(i = 0; i < 15; i ++)
for(j = 0; j < 15; j ++)
{
if(board[i][j] == ' ')
{
for(q = 0; q < 8; q ++)
{
if(k == 0) d = 'b';
else d = 'h';
//左←
if(q == 0 && j >= 0)
{
for(;j - a >= 0;)
{
if(board[i][j-a] == d) {b++; a++; continue;}
else break;
}
qiju[k][i][j][q][0] = b; b = 0;
if(board[i][j-a] == ' ' && j - a >= 0) {qiju[k][i][j][q][1] = 1; a = 1;}
else { qiju[k][i][j][q][1] = 0; a = 1;}
}
//左上↖
if(q == 1 && i >= 0 && j >= 0)
{
for(;i - a >= 0 && j - a >= 0;)
{
if(board[i-a][j-a] == d) {b++; a++; continue;}
else break;
}
qiju[k][i][j][q][0] = b; b = 0;
if(board[i-a][j-a] == ' ' && j - a >= 0 && i - a >= 0) {qiju[k][i][j][q][1] = 1; a = 1;}
else {qiju[k][i][j][q][1] = 0; a = 1;}
}
//上↑
if(q == 2 && i >= 0)
{
for(;i - a >= 0;)
{
if(board[i-a][j] == d) {b++; a++; continue;}
else break;
}
qiju[k][i][j][q][0] = b; b = 0;
if(board[i-a][j] == ' ' && i - a >= 0) {qiju[k][i][j][q][1] = 1; a = 1;}
else {qiju[k][i][j][q][1] = 0; a = 1;}
}
//右上↗
if(q == 3 && i >= 0 && j < 15)
{
for(;i - a >= 0 && j + a < 15;)
{
if(board[i-a][j+a] == d) {b++; a++; continue;}
else break;
}
qiju[k][i][j][q][0] = b; b = 0;
if(board[i-a][j+a] == ' ' && i - a >= 0 && j + a < 15) {qiju[k][i][j][q][1] = 1; a = 1;}
else {qiju[k][i][j][q][1] = 0; a = 1;}
}
//右→
if(q == 4 && j < 15)
{
for(;j + a < 15;)
{
if(board[i][j+a] == d) {b++; a++; continue;}
else break;
}
qiju[k][i][j][q][0] = b; b = 0;
if(board[i][j+a] == ' ' && j + a < 15) {qiju[k][i][j][q][1] = 1; a = 1;}
else {qiju[k][i][j][q][1] = 0; a = 1;}
}
//右下↘
if(q == 5 && i < 15 && j < 15)
{
for(;i + a < 15 && j + a < 15;)
{
if(board[i+a][j+a] == d) {b++; a++; continue;}
else break;
}
qiju[k][i][j][q][0] = b; b = 0;
if(board[i+a][j+a] == ' ' && i + a < 15 && j + a < 15) {qiju[k][i][j][q][1] = 1; a = 1;}
else {qiju[k][i][j][q][1] = 0; a = 1;}
}
//下↓
if(q == 6 && i < 15)
{
for(;i + a < 15;)
{
if(board[i+a][j] == d) {b++; a++; continue;}
else break;
}
qiju[k][i][j][q][0] = b; b = 0;
if(board[i+a][j] == ' ' && i + a < 15) {qiju[k][i][j][q][1] = 1; a = 1;}
else {qiju[k][i][j][q][1] = 0; a = 1;}
}
//左下↙
if(q == 7 && j >= 0 && i < 15)
{
for(;i + a < 15 && j - a >= 0;)
{
if(board[i+a][j-a] == d) {b++; a++; continue;}
else break;
}
qiju[k][i][j][q][0] = b; b = 0;
if(board[i+a][j-a] == ' ' && i + a < 15 && j - a >= 0) {qiju[k][i][j][q][1] = 1; a = 1;}
else {qiju[k][i][j][q][1] = 0; a = 1;}
}
}
}
}
/******************根据评分规则对每一个空格评分***************/
for(k = 0; k < 2; k ++)
for(i = 0; i < 15; i ++)
for(j = 0; j < 15; j ++)
{
if(k == 0) //为白棋评分
{
for(q = 0; q < 4; q ++)
{
//活四
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 4
&& qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 1 )
b += 7000;
//活三
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 3
&& qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 1 )
b += 301;
//活二
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 2
&& qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 1 )
b += 43;
//活一
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 1
&& qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 1 )
b += 11;
//死四
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 4
&& ( (qiju[k][i][j][q+4][1] == 0) || (qiju[k][i][j][q][1] == 0) ) )
b += 7000;
//死三
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 3
&& ( (qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 0)
|| (qiju[k][i][j][q][1] == 0 && qiju[k][i][j][q+4][1] == 1) ) )
b += 63;
//死二
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 2
&& ( (qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 0)
|| (qiju[k][i][j][q][1] == 0 && qiju[k][i][j][q+4][1] == 1) ) )
b += 6;
//死一
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 1
&& ( (qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 0)
|| (qiju[k][i][j][q][1] == 0 && qiju[k][i][j][q+4][1] == 1) ) )
b += 1;
}
if(b == 126 || b == 189 || b == 252) b = 1500;
if(b == 106) b = 1000;
a1[i][j] = b; b = 0;
}
if(k == 1) //为黑棋评分
{
for(q =0 ; q < 4; q ++)
{
//活四
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 4
&& qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 1 )
b += 30000;
//活三
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 3
&& qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 1 )
b += 1500;
//活二
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 2
&& qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 1 )
b += 51;
//活一
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 1
&& qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 1 )
b += 16;
//死四
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 4
&& ( (qiju[k][i][j][q+4][1] == 0) || (qiju[k][i][j][q][1] == 0) ) )
b += 30000;
//死三
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 3
&& ( (qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 0)
|| (qiju[k][i][j][q][1] == 0 && qiju[k][i][j][q+4][1] == 1) ) )
b += 71;
//死二
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 2
&& ( (qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 0)
|| (qiju[k][i][j][q][1] == 0 && qiju[k][i][j][q+4][1] == 1) ) )
b += 7;
//死一
if( (qiju[k][i][j][q][0] + qiju[k][i][j][q+4][0]) == 1
&& ( (qiju[k][i][j][q][1] == 1 && qiju[k][i][j][q+4][1] == 0)
|| (qiju[k][i][j][q][1] == 0 && qiju[k][i][j][q+4][1] == 1) ) )
b += 2;
}
if(b == 142 || b == 213 || b == 284) b = 1500;
if(b == 122) b = 1300;
a2[i][j] = b; b = 0;
}
}
/******************算出分数最高的空位,填写坐标*********************/
for(i = 0; i < 15; i ++)
for(j = 0; j < 15; j ++)
{
if(a1[x1][y1] < a1[i][j])
{ x1 = i; y1 = j; }
}
for(i = 0; i < 15; i ++)
for(j = 0; j < 15; j ++)
{
if(a2[x2][y2] < a2[i][j])
{ x2 = i; y2 = j; }
}
if(a2[x2][y2] >= a1[x1][y1]) //进攻
{
board[x2][y2] = 'h';
r.x = x2 + 1; r.y = y2 + 1;
back[2] = x2; back[3] = y2; //记录当前位置,便于悔棋
}
else //防守
{
board[x1][y1] = 'h';
r.x = x1 + 1; r.y = y1 + 1;
back[2] = x1; back[3] = y1; //记录当前位置,便于悔棋
}
chessboard();
}
11、主函数
int main()
{
char c;
point r;
//设定棋盘大小、颜色
system ("color 8f");
system ("mode con cols=72 lines=43");
system ("title 五子棋游戏!(制作人:周啸天)");
if(!welcome()) exit(1); //输出欢迎界面
InitBoard(); //初始化棋盘
r.x = 8; r.y = 8; //初始化光标
while(1)
{
printf("该您下了!(按ESC退出,按z悔棋)\n");
c = 'b';
play(r);
if(Win(c)) break;
c = 'h';
ComAlgo(r);
if(Win(c)) break;
}
showsusscced(c); //显示获胜
system("pause>nul");
return 0;
}
4.2运行结果及界面介绍
欢迎界面
开始运行中悔棋结束
第五章 后记
该游戏代码实现了五子棋的基本操作,由于受编译器限制故在图形界面上有所欠缺,比如不能将当前下的棋子用不同的颜色表示出来,最后赢了之后将成了的5个棋子用不同颜色表示,显示获胜的几个字不能调大字体等。
关于人机对战代码:
1、得到最大值也许不止一个点,但在我的程序中只选择第一个最大点,这个可以用于个随机数来决定,选择那一个最大值点,也可以对这些最大值点再作进一步的分析。
2、在这个算法中我只考虑了周围有棋子的点,而其它点我没有考虑。
3、可以再更进一步,用这个算法来预测以后的几步棋,再选择预测值最好的一步,这样电脑的AI就更高了。
4、这个算法没有考虑禁手规则(ps:实在没看明白禁手规则啥意思)。