黄金点小游戏的设计与实现
写在前面
经过一个月的软件工程的学习,我们迎来了一种全新的编程方式——结对编程。所谓结对编程,顾名思义,就是两两一组,面对同一个显示器,使用同一个键盘、同一个鼠标,一起分析,一起设计,一起编码,一起完成程序,等等。为什么要结对编程呢?我们知道,每个人都的思想都是有局限的,都会有个人的思维定式。一个人不可能把所有的前因、后果都考虑到位。然而两人一起完成编程,不但可以考虑到更多的算法与思想,更重要的是还能够互相学习别人的优点。可能有人会说,两人一起编程,共用一台电脑,效率会不会很低?经过本次编程经历得出:未必比独立编程的效率低。
说说我的结对伙伴吧:我的结对伙伴是我们班位数不多的一位女生,她不仅花容月貌、如花似玉、玉洁冰清、明艳动人,而且冰雪聪明、温文尔雅、出尘脱俗、至真至纯。当时知道和她是结对伙伴的时候,第一反应就是这次赚大发了,这次结对编程,得好好表现表现。结对伙伴性格很随和,同时也很坚定。在一些细枝末节上她不会计较太多,但在一些关键的问题上,非常的坚定,不到吵一架、说服对方或被对方说服不罢休。对待这次结对编程的态度也很认真,老师刚把题目布置下来,就主动找我商量本次结对编程的需求、设计等。正是和这样一位可爱的结对伙伴,完成了这次的结对编程。哈哈,还是回归正题吧,再这样写下去篇幅恐怕有些过长,读者恐怕会有些不耐烦。
游戏设计
本次结对编程要完成任务是:N个同学(N通常大于10),每人写一个0~100之间的有理数 (不包括0或100),交给裁判,裁判算出所有数字的平均值,然后乘以0.618(所谓黄金分割常数),得到G值。提交的数字最靠近G(取绝对值)的同学得到N分,离G最远的同学得到-2分,其他同学得0分。每次游戏至少可以运行10轮以上,并能够保留每轮比赛结果。
每位玩家的信息以结构体数据结构保存,命名为game。结构体game 的变量包括游戏之前和游戏之后的信息,游戏之前的信息包括每轮游戏的数字输入,游戏只之后的信息包括分数的统计,分别用名为player 和achieve 数组保存。除此之外,还需用到信息输入函数Input、某些人输赢比较函数Campare、裁判比较函数Referee、按分数排序函数Sort 和分数输出函数Output。输赢可能有这些情况:每轮游戏可能有不止一位的玩家出的数是一样的,也就是说每轮游戏可能有不止一家赢家或者输家、可能有的玩家一局都没有赢或一局都没有输,分数和赢的次数和输的次数一样多的玩家一样。同时,还有分数从高到底排序,相同分数的玩家的优先级问题等等。其中,每轮游戏可能有不止一位的玩家出的数是一样的,也就是说每轮游戏可能有不止一家赢家或者输家这个问题是由结对伙伴提出的。同时,结对伙伴还完成了某些人输赢比较函数Campare、按分数排序函数Sort 和分数输出函数Output 等大多数工作。
游戏实现(C语言)
玩家信息保存结构体Game :
struct game { int number; float digit; int score; };
三个变量分别用于保存玩家的编号,玩家输入数字和玩家的分数。
两个结构体全局变量player 和achieve :
game *player[20] = { 0 }; //玩家信息 game *achieve[20] = { 0 }; //玩家成就
两个结构体game 变量player 和achieve 分别用于保存本轮游戏的玩家编号、玩家数字、玩家分数和游戏之后的玩家编号、玩家数字、玩家分数。
信息输入函数Input、某些人输赢比较函数Campare、裁判比较函数Referee、按分数排序函数Sort 和分数输出函数Output:
void Input(int peoplecount); //信息输入 void Campare(int peoplecount, float goldpoint); //比较函数 void Referee(int gamecount, int peoplecount); //输赢判断 void Sort(int peoplecount); //分数排序 void Output(int peoplecount); //分数输出
本轮游戏玩家的输入信息函数Input 实现:
void Input(int peoplecount) { int i; for (i = 0;i < peoplecount;i++) { player[i] = new game; printf("\t第%d号玩家,请输入数字:", i + 1); player[i]->number = i; scanf("%f", &player[i]->digit); } printf("\n"); }
用于分别给玩家的编号和数字赋值,分数已在结构体变量申请书赋初值为0。
某些人输赢比较函数Campare 的实现:
//比较函数 void Campare(int peoplecount, float goldpoint) { int i; float max, min, winpeople = 0, lospeople = 0; max = min = fabs(player[0]->digit - goldpoint); for (i = 0;i < peoplecount;i++) { if (fabs(player[i]->digit - goldpoint) <= min) //找到最接近黄金点的数 { winpeople = player[i]->digit; min = fabs(player[i]->digit - goldpoint); } if (fabs(player[i]->digit - goldpoint) >= max) //找到离黄金点最远的数 { lospeople = player[i]->digit; max = fabs(player[i]->digit - goldpoint); } } for (i = 0;i < peoplecount;i++) { if (player[i]->digit == winpeople) { achieve[i]->score += peoplecount; printf("\t第%d号玩家赢。\n", i + 1); } } for (i = 0;i < peoplecount;i++) { if (player[i]->digit == lospeople) { achieve[i]->score -= 2; printf("\t第%d号玩家输。\n", i + 1); } } printf("\n"); }
通过for循环语句找到最接近黄金点的数和离黄金点最远的数。然后对本轮游戏的赢家和输家进行输出,并统计赢家和输家的分数。
裁判比较函数Referee 的实现:
//输赢判断 void Referee(int gamecount, int peoplecount) { int i; float sum = 0, goldpoint = 0; for (i = 0;i < peoplecount;i++) { sum += player[i]->digit; } printf("\t本轮游戏的平均数为:%f。\n", sum / peoplecount); goldpoint = (sum / peoplecount)*(0.618); printf("\t本轮游戏的黄金点为:%f。\n", goldpoint); printf("\n"); Campare(peoplecount, goldpoint); }
先用变量sum统计出所有玩家的数字和,再除玩家个数,得到玩家数字的平均值,平均值乘0.618,得到玩家数字的黄金点GoldPoint,再调用某些人输赢比较函数Campare ,以实现对玩家输赢的判断。
按分数排序函数Sort 的实现:
void Sort(int peoplecount) { int i, j; for (i = 0;i < peoplecount;i++) { int tempsignal = i, tempnumber = achieve[i]->number, tempscore = achieve[i]->score; tempnumber = achieve[i]->number; tempscore = achieve[i]->score; for (j = i + 1;j < peoplecount;j++) { if (tempscore< achieve[j]->score) { tempsignal = j; tempnumber = achieve[j]->number; tempscore = achieve[j]->score; } } achieve[tempsignal]->number = achieve[i]->number; achieve[tempsignal]->score = achieve[i]->score; achieve[i]->number = tempnumber; achieve[i]->score = tempscore; } }
使用冒泡排序的算法,对游戏后的玩家分数进行从大到小排序。冒牌排序的算法原理,此处不再赘述。
分数输出函数Output 的实现:
void Output(int peoplecount) { int i; for (i = 0;i < peoplecount;i++) { printf("\t第%d号玩家:%d。\n", achieve[i]->number + 1, achieve[i]->score); } }
依次输出玩家的分数。
运行测试(C语言)
游戏运行界面,解读游戏规则及提示输入游戏轮次和玩家个数:
第1轮游戏开始,提示输入各玩家的数字:
本次游戏的平均数为:23.416667,黄金点为:14.471500。8号玩家和11号玩家的数都为:16,最接近黄金点,故8号玩家和11号玩家赢。反之,4号玩家、7号玩家和10号玩家的数为:34,偏离黄金点最远,故4号玩家、7号玩家和10号玩家输。
8号玩家和11号玩家加12分,4号玩家、7号玩家和10号玩家减2分。游戏继续。
第2轮游戏开始:
第2轮游戏之后成绩:
第3轮游戏开始:
游戏继续...
第12轮游戏开始:
第12轮游戏之后成绩:
宣布本次游戏结果,结果按成绩从高到底排序:
游戏运行结束,选择0退出游戏。
游戏实现(C#语言)
程序入口:
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; namespace GoldPoint_Game { static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new frmWelcome()); } } }
在program类中,首先运行程序欢迎窗体。
欢迎窗体:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace GoldPoint_Game { public partial class frmWelcome : Form { public frmWelcome() { InitializeComponent(); } private void textBox1_TextChanged(object sender, EventArgs e) { } private void frmWelcome_Load(object sender, EventArgs e) { } private void button1_Click(object sender, EventArgs e) { GoldGame f1 = new GoldGame(textBox2.Text,textBox3.Text); f1.ShowDialog(); this.Close(); } private void textBox2_TextChanged(object sender, EventArgs e) { } private void button2_Click(object sender, EventArgs e) { this.Close(); } } }
欢迎窗体的代码实现,在该类中,实现了对欢迎窗体的初始化,包括游戏规则的提示,游戏轮次、玩家个数的输入,退出游戏、进入游戏提示等。
游戏窗体:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace GoldPoint_Game { partial class GoldGame : Form {
......
}
}
游戏窗体时该程序的主体部分,下面进行详细的讲解。
一个Goldgame类和两个player,achieve列表:
class Goldgame { public int number; public float digit; public int score; }; List<Goldgame> player = new List<Goldgame> { }; List<Goldgame> achieve = new List<Goldgame> { };
两个player,achieve列表分别用于保存游戏玩家的编号、数字、分数和游戏成就的编号、数字、分数。
两个整型变量gameCount、peopleCount:
private int gameCount; private int peopleCount; public int GameCount { get { return gameCount; } set { gameCount = value; } } public int PeopleCount { get { return peopleCount; } set { peopleCount = value; } } int rgameCount; int rpeopleCount; public GoldGame(string str1,string str2) { InitializeComponent(); this.gameCount = Convert.ToInt32(str1); this.peopleCount = Convert.ToInt32(str2); } public GoldGame() { InitializeComponent(); }
两个整型变量gameCount和peopleCount用于接收欢迎窗体传过来的游戏轮次和游戏玩家个数的值,通过函数重载的方法实现。
游戏窗体Load事件:
private void Form1_Load(object sender, EventArgs e) { int i; rgameCount = 1; rpeopleCount = 1; for (i = 0; i <= peopleCount; i++) { Goldgame players = new Goldgame(); players.number = rpeopleCount-1; players.digit = 0; players.score = 0; player.Add(players); Goldgame achieves = new Goldgame(); achieves.number = rpeopleCount-1; achieves.digit = 0; achieves.score = 0; achieve.Add(achieves); rpeopleCount++; } rpeopleCount = 1; label3.Text = "第" + rgameCount + "轮游戏:"; textBox1.Text = Convert.ToString(rpeopleCount); textBox2.Text = "0"; }
该事件中,实现了对玩家信息列表和游戏成就列表的赋初值,以及对各个控件的信息设置。
游戏输赢比较函数Campare:
void Campare(float goldpoint) { int i; float max, min, winpeople = 0, lospeople = 0; max = min = System.Math.Abs(player[1].digit - goldpoint); for (i = 1; i <= peopleCount; i++) { if (System.Math.Abs(player[i].digit - goldpoint) <= min) //找到最接近黄金点的数 { winpeople = player[i].digit; min = System.Math.Abs(player[i].digit - goldpoint); } if (System.Math.Abs(player[i].digit - goldpoint) >= max) //找到离黄金点最远的数 { lospeople = player[i].digit; max = System.Math.Abs(player[i].digit - goldpoint); } } for (i=1; i <= peopleCount; i++) { achieve[i].digit = player[i].digit; } listBox1.Items.Add('\n'); if (winpeople == lospeople) { listBox1.Items.Add("玩家数字一样,故不判断输赢!"); } else { for (i = 1; i <= peopleCount; i++) { if (player[i].digit == winpeople) { achieve[i].score += peopleCount; listBox1.Items.Add("第" + i + "号玩家赢。"); } } for (i = 1; i <= peopleCount; i++) { if (player[i].digit == lospeople) { achieve[i].score -= 2; listBox1.Items.Add("第" + i + "号玩家输。"); } } } listBox1.Items.Add('\n'); for (i = 1; i <= peopleCount; i++) { listBox1.Items.Add("第" + i + "号玩家," +"数字为:"+achieve[i].digit+ ",分数为:" + achieve[i].score); } }
算法思想同C语言实现,此处不再赘述。
裁判函数Referee:
private void Referee() { int i; float sum = 0, goldpoint = 0; for (i = 1; i <= peopleCount; i++) { sum += player[i].digit; } label3.Text = "第"+rgameCount+"轮游戏结果如下:"; listBox1.Items.Clear(); listBox1.Items.Add("第"+rgameCount+"轮游戏平均数为:"+sum/peopleCount); goldpoint = Convert.ToSingle(sum / peopleCount * 0.618); listBox1.Items.Add("第" + rgameCount + "轮游戏黄金点为:" + sum / peopleCount*0.618); Campare(goldpoint); }
该函数实现对平均数、黄金点的计算和输出,同时调用了输赢比较函数,实现了把对玩家输赢的输出以及各个玩家成绩输出到listBox1中。
输入分数按钮的Click事件:
private void button1_Click(object sender, EventArgs e) { if (rpeopleCount < peopleCount) { listBox1.Items.Add(Convert.ToString("玩家编号:"+ rpeopleCount+","+"玩家数字:" + textBox2.Text)); player[rpeopleCount].number = rpeopleCount; player[rpeopleCount].digit = Convert.ToSingle(textBox2.Text); rpeopleCount++; } else if((rpeopleCount == peopleCount)) { listBox1.Items.Add(Convert.ToString("玩家编号:" + rpeopleCount + "," + "玩家数字:" + textBox2.Text)); player[rpeopleCount].number = rpeopleCount; player[rpeopleCount].digit = Convert.ToSingle(textBox2.Text); textBox1.Text = Convert.ToString(rpeopleCount); listBox1.Text = "第"+rgameCount+"游戏成绩如下:"; Referee(); MessageBox.Show("全部玩家已输入数字,请点击下一轮游戏!", "输入提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } textBox1.Text = Convert.ToString(rpeopleCount); }
该事件中,实现对玩家信息的输入,同时显示到listBox1中,输入完毕,宣布本轮游戏的游戏成绩并提示进入下一轮。
下轮游戏按钮的Click事件:
private void button4_Click(object sender, EventArgs e) { if (rgameCount < gameCount) { rgameCount++; label3.Text = "第" + rgameCount + "轮游戏:"; rpeopleCount = 1; textBox1.Text = Convert.ToString(rpeopleCount); listBox1.Items.Clear(); } else { label3.Text = "第" + rgameCount + "轮游戏:"; textBox1.Text = Convert.ToString(rpeopleCount); MessageBox.Show("游戏结束,请点击公布成绩!", "输入提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } }
该事件中,实现了对每轮游戏的玩家编号的初始化及各个控件的设置。若游戏结束,则会提示游戏结束并提示点击公布成绩。
按分数从高到底排序函数Sort:
private void Sort() { int i, j; for (i = 1; i <= peopleCount; i++) { int tempsignal = i, tempnumber = achieve[i].number, tempscore = achieve[i].score; tempnumber = achieve[i].number; tempscore = achieve[i].score; for (j = i + 1; j <= peopleCount; j++) { if (tempscore < achieve[j].score) { tempsignal = j; tempnumber = achieve[j].number; tempscore = achieve[j].score; } } achieve[tempsignal].number = achieve[i].number; achieve[tempsignal].score = achieve[i].score; achieve[i].number = tempnumber; achieve[i].score = tempscore; } }
该函数实现了对分数从高到底的排序,具体实现同C语言,此处不再赘述。
公布成绩按钮的Click事件:
private void button2_Click(object sender, EventArgs e) { Sort(); int i; label3.Text = "游戏分数排名:"; listBox1.Items.Clear(); for (i = 1; i <= peopleCount; i++) { listBox1.Items.Add("玩家编号:" + achieve[i].number + "," + "玩家分数:" + achieve[i].score); } }
该事件实现了把各个玩家成就输出到listBox1中。并设置各个控件。
重新开始按钮的Click事件:
private void button3_Click(object sender, EventArgs e) { player.Clear(); achieve.Clear(); int i; rpeopleCount = 1; rgameCount = 1; for (i = 0; i <= peopleCount; i++) { Goldgame players = new Goldgame(); players.number = rpeopleCount-1; players.digit = 0; players.score = 0; player.Add(players); Goldgame achieves = new Goldgame(); achieves.number = rpeopleCount-1; achieves.digit = 0; achieves.score = 0; achieve.Add(achieves); rpeopleCount++; } rpeopleCount = 1; label3.Text = "第" + rgameCount + "轮游戏:"; textBox1.Text = Convert.ToString(rpeopleCount); listBox1.Items.Clear(); }
该事件同游戏窗体的Load事件,实现了对玩家信息列表和游戏成就列表的清空,游戏轮次、游戏玩家的初始化以及各个控件的设置。
退出游戏按钮的Click事件:
private void button5_Click(object sender, EventArgs e) { this.Close(); }
该事件实现对窗口的关闭。
运行测试(C#语言)
游戏欢迎界面:
游戏欢迎界面,包括对游戏规则的说明,以及游戏轮次和玩家个数的输入,点击进入游戏按钮进入游戏,点击退出游戏按钮退出游戏。
游戏界面:
玩家编号自动累加,玩家分数需玩家手动填入,玩家数字初始化为0。
玩家数字的输入:
输入之后点击输入分数按钮,玩家信息添加到左面的listBox1中。若为最后一个玩家,则提示点击下轮游戏。玩家数字输入完毕后,计算出该轮游戏的平均数和黄金点,宣布输家和赢家,显示各个玩家该轮游戏的数字和积累分数。
玩家数字输入完毕提示:
玩家数字输入完毕提示:
第一轮游戏数字输入及成就公布:
第一轮游戏数字输入及成就公布:
第二轮游戏数字输入及成就公布:
第二轮游戏数字输入及成就公布:
游戏继续...
游戏结束提示:
游戏结束提示:
游戏结束提示,提示点击公布成绩。
公布成绩:
重新开始:
若需进行下轮游戏,则继续输入数字分数,继续游戏。如需退出游戏,点击退出游戏,关闭程序。
结对总结
编程习惯总结:本次结对编程过程中,我和结对伙伴都对代码规范的要求都很高,经过几次的一起结对编程,似乎都形成了一种默契,例如:if-else语句中假如只有一条语句,也要打上花括号“{”和“}”、变量命首字母小写、复杂变量名用几个单词组合的方式、尽量不把多条语句子放在一行、功能实现函数首字母大写、花括号“{”和“}”独占一行等等。我和结对伙伴非常追求算法的效率,尽量减少代码的重复率。界面也很讲究,界面都追求美观、实用、简洁,要有适当的文字提示等等。
编程过程体会:本次结对编程是软件工程课程作业的第二阶段,通过了个人项目开发阶段的磨砺,确实对软件工程的感触越来越深:编程不是简简单单的堆砌代码,它是一种对思想的表达,其中包含着很多的理论与思想,它是一种情绪的寄托,其中包含着很多的幸勤和汗水。软件工程既然称之为工程,它不是一件微小的事情,就不是一个人能够胜任的。它需要依靠一群人的共同努力,一套相对完整的工作流程,一套相对完美的解决方案。谢谢结对伙伴的鼓励和帮助,让这次结对编程完整的落下了帷幕。
附:
结对编程工作照:
工作照一
工作照二
结对伙伴博客园地址:http://www.cnblogs.com/sunxingzi。
结对伙伴Coding主页地址:https://coding.net/u/sunxingzi。
本人Coding主页地址:https://coding.net/u/mingrenjiang。