黄金点游戏
黄金点游戏是一个数字小游戏,其游戏规则是:
N个同学(N通常大于10),每人写一个0~100之间的有理数 (不包括0或100),交给裁判,裁判算出所有数字的平均值,然后乘以0.618(所谓黄金分割常数),得到G值。提交的数字最靠近G(取绝对值)的同学得到N分,离G最远的同学得到-2分,其他同学得0分。玩了几天以后,大家发现了一些很有意思的现象,比如黄金点在逐渐地往下移动。
现在请大家根据这个游戏规则,编一个可以多人一起玩的小游戏程序,要求如下:
1、本作业属于结对编程项目,必须由二人共同完成,并分别将本次作业过程发到博客,同时将本次作业源代码提交到codeing系统;
2、如果可能的话尽量以C/S或B/S方式实现,即利用服务器接收和处理所有玩家提交的数字,并将结果反馈给各玩家,玩家可以通过客户端提交的数字;
3、如果采用单机方式实现的话,需要为用户提供便利的输入界面;
4、该游戏每次至少可以运行10轮以上,并能够保留各轮比赛结果。
以上就是我们此次的结对编程的题目与规则了。
总体结构如图所示:
这是我们代码上传的coding主页地址:https://coding.net/u/xiaoyongwu
上述完了题目与设计,下面就该我们的睿哥登场了,也是本次我结对编程的队友——杨睿。
这是杨睿同学的博客地址:http://www.cnblogs.com/ruiguo/
以下是我们准备开始编程的照片、编程过程中的照片、以及最后进行调试和测试的照片:
杨睿同学,是一个来自湖南的帅哥,但是可能由于在北方读书的缘故,所以在我们班性格比较内向,不爱说话,但是对人很好,只要你找他帮忙,他肯定会竭尽全力的帮你。 在此次的结对编程中,杨睿因为以前C语言学的一般,所以对于此次的编程还是有很大的不自信的。后来我们俩经过沟通,他对于此次的编程慢慢开始充满了期待,希望能从中学到一些东西,并且在之后的结对编程中也体现出来了杨睿的信心、兴趣以及内心的想学好编程的想法。最终我们俩确定了计算黄金点,计算各轮次的得分以及玩家信息的建立给杨睿来写,其他的功能由我写。
在编程的过程当中我们遇到了很多问题,由于以前我们用结构数组用的比较少,而这次我们俩是用的结构数组的存储玩家信息,先动态的分配了M个玩家的地址,然后再往其中填上数据,所以在内存的分配与释放上我们俩遇到了小小的困难。事实证明两个人编程比一个好,不到一会儿,我们俩就弄清事情的原委。还有一次我们俩在结构数组指针的传递上出现了分歧,因为用的结构数组,需要传递给子函数数组指针,在用一个*还是两个*问题上我们俩出现了初学者的困惑,但是后来一看C语言书就明白过来了。当然还有其他的一些小问题,这儿就不一一列举了。
在代码规范上面,我觉得我们俩人还是比较默契的,因为是我先写的代码,轮到杨睿的时候,他基本上是照着我之前的代码风格写下来的,尽管中间出现了一些小的问题,但是都不大,就像把两个整数类型数据赋初值,杨睿喜欢连续赋值,例如:int a=0,b=1;而我比较喜欢int a=0;int b=1;所以基本上还是问题不大。还是以前大一的时候老师要求的好,上机时老师都给我们要求了代码的编写规范(当然只是相对来说让人更容易读懂),而我们也一直学在手中的。
总的来说,此次的编程我觉得是成功的,尽管我们俩个没有实现C/S或者B/S方式,或者提供便利的美观界面设计。但是我觉得这不是最重要的,重要的是我们俩都能从此次编程中学到什么,也许我们都会有抱大腿的心态,跟着大神“做”,然后就真的是座了。也许我们俩现在是刚刚起步,但是只要基础打得深,相信以后建高楼,盖大厦不会是难事,张狂点说甚至是轻而易举。两个人一起编程的好处在于多了一双眼睛帮助你检查错误,就像平常写字一样,一疏忽(紧张)熟悉的字也写错,有人看着可以时时提醒,特别是当你跟着一个比你强的人编程的时候,你将学到更多东西(尽管目前我还没体验过,但是一直在寻找这种成长的机会)。还有一个好处就是,如果有一天你生病了,但是又需要提交程序,如果一个能看你程序的人都没有,那么只能另寻它法了(当然这只是个比方,并不代表实际)。总之结对编程的好处很多,需要我们慢慢去探寻,只有自己亲自去找寻的东西才会是自己体会最深的、记忆最深刻的。
下面讲讲我们的程序吧:
首先我们用了一个people结构体,保存了玩家的序号、提交的数字、成绩等等信息,并且起了一个PLAYER的别名。紧跟着的是我们的子函数,包括:传值函数、录取信息函数、排序函数、释放内存函数、计算函数、显示函数等等。
struct People { int n_num;//游戏人员序号 float digit;//所选数字 float gap;//所选数字与黄金数字的差距 int S_score;//本轮成绩 int score;//总成绩 }; typedef struct People PLAYER; PLAYER *give(PLAYER **ppla,int num);//把ppla赋给ppla2 PLAYER *Getinf(PLAYER **ppla,int i,int m);//获取玩家信息 void sort_S_score(PLAYER **ppla2,int num);//每轮游戏之后进行选择排序 void sort_score(PLAYER **ppla,int num);//游戏结束之后按照总分进行选择排序 void freememory(PLAYER **ppla,int num);//释放结构体数组的内存 float count_gold(PLAYER **ppla,int num);//计算黄金点 void count_score(PLAYER **ppla,int num,float g_num);//计算分数 void show(PLAYER **ppla2,int num);//显示每轮游戏结果
接下来是该程序最重要的主函数部分:
主函数是这样设计的:首先定义了count变量,用做判断是否循环玩游戏,输入玩家人数Num以及游戏的轮次数id,然后动态分配结构体数组的空间给ppla、ppla2以及创建一个保存游戏每轮得分信息的文件gold.txt。再依次建立每个玩家的信息,然后根据所输入的信息计算出黄金点G值,然后在根据G值计算出每个玩家提交的数字与G值的距离,并且给与相应的分数。再把得分信息存入文件当中,然后根据得分把每一轮进行排序。等到最后一轮结束,再根据总成绩进行排序,并且从文件中直接读取信息输出每位玩家在每一轮当中的得分情况等。最后询问是继续游戏还是退出游戏。
void main(int argc, char *argv[]) { int count=1; while(count) { int id=0,num=0; view(id,num); //动态建立结构体数组 PLAYER **ppla; PLAYER **ppla2; ppla=(PLAYER **)malloc (num * sizeof(PLAYER *)); if(ppla==NULL) { printf("not set the ppla "); return ; } ppla2=(PLAYER **)malloc (num * sizeof(PLAYER *)); if(ppla2==NULL) { printf("not set the ppla2 "); return ; } //打开文件 FILE *fp; fp=fopen("gold.txt","w"); if(fp==NULL) { printf("cano't open gold.txt "); exit(0); } //开始每一轮的玩家信息输入、最终结果的输出 int m=1; while(id) { printf("-----第%d轮----- ",m); //建立每个玩家的信息 int i=0; for(i=0;i<num;++i) { ppla[i]=Getinf(ppla,i,m); if(ppla[i]==NULL) { printf("error "); freememory(ppla,i); return; } } //计算黄金点 float g_num=0; g_num=count_gold(ppla,num); //计算得分 count_score(ppla,num,g_num); //把本轮信息存入文件中 int g=0; for(g=0;g<num;++g) { fprintf(fp," %d %f %f %d %d ",ppla[g]->n_num,ppla[g]->digit,ppla[g]->gap,ppla[g]->S_score,ppla[g]->score); } //把ppla的值给ppla2并对本轮排序 int s=0; for(s=0;s<num;++s) { ppla2[s]=give(ppla,s); } sort_S_score(ppla2,num); //输出每一轮的排名得分情况 printf(" 黄金点数字为:%f ",g_num); show(ppla2,num); int cls=0; printf("是(1)否(0)清屏? "); scanf("%d",&cls); if(cls==1) system("CLS"); m++; id--; } fclose(fp); //按照总分排序 int yy=0; printf("=====游戏已结束,是(1)否(0)按照总分成绩进行排序? "); scanf("%d",&yy); if(yy==1) { sort_score(ppla,num); show(ppla,num); } //查看每一轮成绩 int xx=0,xy=0,g=0; m=m-1; xy=num; printf(" =====游戏已结束,是(1)否(0)查看各轮比赛成绩? "); scanf("%d",&xx); if(xx==1) { char ch[100]; fp=fopen("gold.txt","r"); num=m*num; int j=1; for(g=0;g<num;++g) { fgets(ch,50,fp); if((g%xy)==0) { printf(" 第%d轮情况 ",j); j=j+1; printf(" 序号 提供数字 黄金间距 本轮成绩 总成绩 "); } printf("%s",ch); } } freememory(ppla,xy); freememory(ppla2,xy); printf("====继续游戏请输入(1),退出游戏请输入(0)====== >>>"); scanf("%d",&count); if(count==1) system("CLS"); } }
接下来介绍一下显示函数:显示函数包括两个,一个是输出每轮游戏的结果,还有一个是输出游戏规则等,直接用输出语句就行了。
//显示每轮游戏结果 void show(PLAYER **ppla2,int num) { int i=0; printf(" 排名情况 "); printf(" 序号 提供数字 黄金间距 本轮成绩 总成绩 "); for(i=0;i<num;++i) { printf(" %d %f %f %d %d ",ppla2[i]->n_num,ppla2[i]->digit,ppla2[i]->gap,ppla2[i]->S_score,ppla2[i]->score); } } void view(int &id,int &num) { printf("===================================================================== "); printf(" Welcome To The Gold Game "); printf("---------------------------------------------------------------------- "); printf("游戏规则:N个同学(N通常大于10),每人写一个0~100之间的有理数 "); printf(" (不包括0或100),交给裁判,裁判算出所有数字的平均值,然后乘以 "); printf(" 0.618(所谓黄金分割常数)得到G值。提交的数字最靠近G(取绝对值) "); printf(" 的同学得到N分,离G最远的同学得到-2分,其他同学得0分。 "); printf("---------------------------------------------------------------------- "); printf(" ==========请输入玩家人数========== >>"); scanf("%d",&num); printf(" ==========请输入游戏轮数========== >>"); scanf("%d",&id); }
然后是获取玩家信息函数,给子函数传入结构体数组指针,玩家人数,游戏轮次等,第一轮的时候需要给结构体数组中添加每个玩家的结构体,需要动态分配新结构体,然后把结构体按照序号放入ppla当中,并且给玩家的提交数字、总分、每轮得分、间距等赋初值。
//获取玩家信息 PLAYER *Getinf(PLAYER **ppla,int i,int m) { if(m==1) { PLAYER *p; p=(PLAYER *) malloc (sizeof(PLAYER)); if(p==NULL) { return NULL; } p->n_num =i+1; printf(" 请输入第%d个玩家选择的有理数字(0-100) >>>>",i+1); scanf("%f",&p->digit ); printf(" "); p->gap =0; p->S_score =0; p->score =0; return (p); } else { printf(" 请输入第%d个玩家选择的有理数字(0-100) >>>>",i+1); scanf("%f",&ppla[i]->digit ); printf(" "); ppla[i]->gap =0; ppla[i]->S_score =0; return (ppla[i]); } }
下面介绍的是计算函数,计算函数也是两个,一个是计算黄金点,一个是计算得分情况。首先跟据玩家提供的数字进行求平均值,然后*0.618再返回黄金点值。再根据黄金点值计算出每位玩家提供的数字与gold_num的距离,找到最小的min和最大的max(其中包含了多个人提交相同数字的情况),然后在给相应的玩家分数。
//计算黄金点 float count_gold(PLAYER **ppla,int num) { float sum=0,gold=0; int j=0; for(j=0;j<num;++j) { sum += ppla[j]->digit; } sum=sum/num; gold = float(sum * g_gold); return gold; } //计算分数 void count_score(PLAYER **ppla,int num,float g_num) { //把间距计算出来存入结构体中 int k=0; float min=100,max=0; for(k=0;k<num;++k) { ppla[k]->gap = float(fabs(ppla[k]->digit - g_num)); //找到最近的数字 if(ppla[k]->gap < min) { min=ppla[k]->gap ; } //找到最远的数字 if(ppla[k]->gap >max) { max=ppla[k]->gap ; } } for(k=0;k<num;++k) { //避免有很多人选中同一个数字的情况 //本轮成绩为+2、0、-2,总成绩加(减)2 if(ppla[k]->gap ==min) { ppla[k]->S_score =num; ppla[k]->score =ppla[k]->score +ppla[k]->S_score; } else if(ppla[k]->gap ==max) { ppla[k]->S_score =-2; ppla[k]->score =ppla[k]->score +ppla[k]->S_score; } else { ppla[k]->score =ppla[k]->score +ppla[k]->S_score ; } } }
下面是排序函数,排序我们选用的是简单的选择排序,直接对每一轮的得分、总分进行排序。
//每轮游戏之后按照本轮得分情况进行选择排序 void sort_S_score(PLAYER **ppla2,int num) { PLAYER *p; int i,j,k; for(i=0;i<num-1;++i) { k=i; for(j=i+1;j<num;++j) { if(ppla2[j]->S_score > ppla2[k]->S_score ) { k=j; } } if(k!=i) { p=ppla2[i]; ppla2[i]=ppla2[k]; ppla2[k]=p; } } } //游戏结束之后按照总分进行选择排序 void sort_score(PLAYER **ppla,int num) { PLAYER *p; int i,j,k; for(i=0;i<num-1;++i) { k=i; for(j=i+1;j<num;++j) { if(ppla[j]->score > ppla[k]->score ) { k=j; } } if(k!=i) { p=ppla[i]; ppla[i]=ppla[k]; ppla[k]=p; } } }
最后就是内存的释放了:当玩家退出游戏后,直接把动态数组销毁,释放内存。
//释放内存 void freememory(PLAYER **ppla,int num) { int i; for(i=0;i<num;++i) { free(ppla[i]); } free(ppla); }
下面是运行截图:
测试一:11个玩家,玩4轮游戏,前两轮游戏每个玩家输入数据相同,得出结果是:一个玩家满分,一个玩家负满分,其他人0分。后两轮随意输入,得出并不是提交的数越小越容易得分,取决于大家总体的提交数字,相对来说在提交的数当中处于中等偏下的数字越有优势。
测试二:5个玩家,玩4轮,第一轮所有玩家输入相同的数字,得出所有玩家都得分的结论;第二,三,四轮随意输入,计算得分。最后一张为gold.txt中保存的每轮游戏信息。
测试黄金点的规律:
使用的随机函数生成0-100之间的有理数,然后用全局变量定义了一个数组保存黄金点数字,输出每次游戏之后的黄金点数字。
发现机器生成的数字,让黄金点数字在30左右徘徊:
这是5人10轮的截屏:
这是10人20轮的截屏:
这是20人30轮的截屏: