结对编程作业

douyacai/stForms (github.com)

姓名 团队分工 博客链接
李霆政 窗口实现、游戏逻辑编写、AI算法编写、部分原型设计 https://www.cnblogs.com/douyacaizuikeai/p/15450801.html
胡驰 前端设计,AI算法,原型设计 https://www.cnblogs.com/Microsoft-hc/p/15450112.html

一、原型设计

https://orgnext.modao.cc/app/d8fd671e328529e898aaaa43346c35c970c7a56f#screen=skv4jrlj8shdvcz
我们组的风格比较清奇,我们是先用Adobe Photoshop将产品的一些界面画出,然后再用Visual Stdio进行交互设计,后来发现设计的跟最后所得到的结果差距蛮大的,然后由于实在看不下去了,就屁颠屁颠速成墨刀这种比较流行的原型工具
在PS上画的背景图是这样的:

还有这样的:

然而真正等我们去自己写出这个界面时,它变成了这样:

还有这样:

效果差了不少,但总的来说还算看的过去(毕竟就像是自己的亲儿子一样),可是毕竟是作业还是得认真点对待
后来我们就去墨刀上设计界面,发现真的好用的一批,界面设计最后是这样子的:
使用墨刀后的登录界面:

墨刀对战界面:

墨刀联机登录界面:

墨刀规则说明:

真是好看了不少,hhhh。

[2.2.2]遇到的困难及解决方法:(4分)

  • 困难描述(1分)
    第一次纯自己设计原型弄出来的界面像极了二十年前的东西,太过粗糙,又没有好的优化方法(不借助其他软件的话)

  • 解决过程(2分)
    重新回看博客要求,发现老师提供了设计软件,顿时懊恼自己怎么和个sb一样,于是连夜速成了墨刀这种流行原型设计软件,效果之好显而易见!!!

  • 有何收获(1分)
    熟悉了原型设计工具的使用方法,更加确信了要自习看看博客作业的内容,里面都藏着一些意料之外的惊喜,同时再一次让我感受到,社会在发展,人类在进步,果然流行的工具自然有它自己流行的原因。

二、原型设计实现

[2.3.1]代码实现思路: (11分)

  • 网络接口的使用
    孩子实在是没做出来。

  • 代码组织与内部实现设计(类图)

  • 说明算法的关键与关键实现部分流程图
    算法思路:
    由于双方的牌型都是公开可见的,为了能够赢得游戏胜利,人类玩家会将自己的牌型始终保持为均牌型,因此电脑只需要保证在自己也是均牌型的同时避免去出和牌顶牌一样的牌型即可
    为了取得游戏胜利,游戏玩家通常会选择优先出掉手里的牌,来保证牌数最小,此时可以计算双方的牌数差,
    到最后时刻,必然是会出现一方出牌,一方抽牌的局面。这样双方抽牌数就会对半分。
    出牌方会是牌数较多的一方
    抽牌方则为牌数较少的一方
    电脑方想要赢得胜利就应该保证前期尽可能多拿牌,通过简单计算,发现要将牌分为4份,一份是在剩下牌堆里,一份在玩家手上,另两份在电脑手里,虽然玩家和牌堆加起来才和电脑总数相等,但是在后续的出牌过程中,由于电脑的牌力远高于玩家的,因此,在临近比赛尾声的一轮出牌中,玩家如果想一直摸牌结束游戏,然而最终玩家的排队剩下的牌加起来才可能等于电脑中的牌(而且还是算在电脑未出牌前的),因此玩家最终必回拿到剩下牌堆里的牌,此后电脑则一直摸牌结束游戏即可,考虑到后续玩家会一直出牌而非摸牌,最终电脑在临近比赛结束时(以13张牌为一个参考),应在前期保证手里有占牌总数2/5(即21张牌)的均牌型,玩家的牌占总数1/5(即10-11张牌),或是电脑手牌始终在牌堆还剩13张牌前,保持为玩家的2倍少一点(具体为少两三张)。此后则一直打出手里的牌,直到玩家全部收走打出的牌,此后则一直摸牌即可游戏结束取得胜利

算法流程图:

  • 贴出你认为重要的/有价值的代码片段,并解释(2分)
玩家类定义:
class Player : IDisposable
    {
        private Stack<poker> spade; //♠
        private Stack<poker> heart; //♥
        private Stack<poker> club; //♣️
        private Stack<poker> diamond; //♦
        public Player()
        {
            this.spade = new Stack<poker>();
            this.heart = new Stack<poker>();
            this.club = new Stack<poker>();
            this.diamond = new Stack<poker>();
        }
        #region 出牌操作
        public poker get_Spade()
        {
            return this.spade.Pop();
        }
        public poker get_Heart()
        {
            return this.heart.Pop();
        }
        public poker get_Club()
        {
            return this.club.Pop();
        }
        public poker get_Diamond()
        {
            return this.diamond.Pop();
        }
        #endregion

        #region 收牌操作
        public void push_Diamond(poker temp)
        {
            this.diamond.Push(temp);
        }
        public void push_Spade(poker temp)
        {
            this.spade.Push(temp);
        }
        public void push_Heart(poker temp)
        {
            this.heart.Push(temp);
        }
        public void push_Club(poker temp)
        {
            this.club.Push(temp);
        }
        #endregion

        #region 获取手牌术
        public int H_num()
        {
            return this.heart.Count();
        }
        public int C_num()
        {
            return this.club.Count();
        }
        public int D_num()
        {
            return this.diamond.Count();
        }
        public int S_num()
        {
            return this.spade.Count();
        }
        public int Poker_Sum()
        {
            return this.S_num() + this.H_num() + this.D_num() + this.C_num();
        }
        #endregion
注释:对玩家类操作的定义就是定义了玩家所具有的手牌,然后对自己手牌的操作进行,通过栈操作获取手牌信息,以及对出牌、收牌的操作。
卡牌类定义:
class poker : IDisposable
    {
        private string num; // 卡牌号
        private string card; // 卡牌花色

        public poker() // 空构造函数
        {

        }

        public poker(string Poker_card, string Poker_num) // 构造函数
        {
            this.num = Poker_num;
            this.card = Poker_card;
        }

        public poker Create_poker(string Poker_num, string Poker_card)
        {
            this.num = Poker_num;
            this.card = Poker_card;
            return this;
        }

        public string getPoker_card() // 获取卡牌数
        {
            return this.card;
        }

        public string getPoker_num() // 获取卡牌号
        {
            return this.num;
        }

        public string getCard_Internet() // 处理一些特殊数字的卡牌
        {
            string pokerInfo = this.num;
            if (int.Parse(this.num) > 10)
            {
                if (this.num == "11") pokerInfo += "J";
                else if (this.num == "12") pokerInfo += "Q";
                else pokerInfo += "K";
            }
            else pokerInfo += this.num;
            return pokerInfo;
        }

        public string getPokerImg() // 将卡牌信息整合处理对应上图片信息
        {
            string poker_Type = "";
            if (this.card == "S") poker_Type = "黑桃";
            else if (this.card == "H") poker_Type = "红桃";
            else if (this.card == "D") poker_Type = "梅花";
            else poker_Type = "方块";
            return poker_Type + this.num + ".jpg";
        }

        public bool Equal(poker another)
        {
            if (this.num == another.num &&
                this.card == another.card) return true;
            return false;
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }
注释:在定义卡牌类时就设计好了一些信息获取函数,便于后期使用。
卡牌处理模块:
#region 卡牌处理
        public void create_card() // 生成卡牌
        {
            int Card_count = 0;
            for (int i = 1; i <= 13; i++, Card_count++)
            {
                Card_Lib[Card_count] = new poker("S", Convert.ToString(i));
            }
            for (int i = 1; i <= 13; i++, Card_count++)
            {
                Card_Lib[Card_count] = new poker("H", Convert.ToString(i));
            }
            for (int i = 1; i <= 13; i++, Card_count++)
            {
                Card_Lib[Card_count] = new poker("C", Convert.ToString(i));
            }
            for (int i = 1; i <= 13; i++, Card_count++)
            {
                Card_Lib[Card_count] = new poker("D", Convert.ToString(i));
            }

        }
        public void poker_shuffle()  // 洗牌操作
        {
            for (int i = 0; i < 52; i++)
            {
                this.Card_tmp = new poker();
                int index = randint.Next(52);
                Card_tmp = Card_Lib[i];
                Card_Lib[i] = Card_Lib[index];
                Card_Lib[index] = Card_tmp;
            }
        }
        public void push_poker() // 将洗好的牌放入牌堆栈中
        {

            for(int i = 0; i < 52; i++)
            {
                poker_Lib.Push(Card_Lib[i]);
            }
        }
        public int poker_judge()
        {
            if(poker_tmp.Count() == 0)
            {
                return 2;
            }
            else if(this.Card_tmp.getPoker_card() == poker_tmp.Peek().getPoker_card())
                return 1;
            else
                return 2;
        }
        #endregion
注释:将卡牌的处理模块化在后期使用就会方便时,将这些函数一起调用时就是一个洗牌操作生成一副打乱过得牌组。
玩家收牌操作:
public void Licensing_poker(Player py)
        {
            int num = poker_tmp.Count();
            for(int i = 0; i < num; i++)
            {
                this.Card_tmp = new poker();
                this.Card_tmp = poker_tmp.Pop();
                if(this.Card_tmp.getPoker_card() == "S")
                {
                    py.push_Spade(this.Card_tmp);
                }
                else if (this.Card_tmp.getPoker_card() == "H")
                {
                    py.push_Heart(this.Card_tmp);
                }
                else if(this.Card_tmp.getPoker_card() == "C")
                {
                    py.push_Club(this.Card_tmp);
                }
                else if(this.Card_tmp.getPoker_card() == "D")
                {
                    py.push_Diamond(this.Card_tmp);
                }
            }
        }
注释:此操作就是在需要收牌是将牌收入手中的牌栈中。
  • 性能分析与改进

  • 描述你改进的思路(2分)
    因为没有动画效果,程序通过进程休眠进行视觉停留,才能让玩家获取对局信息翻牌信息,就加大了程序时间的使用。在窗口界面的代码结构化不是特别好,程序的函数的调用就不是非常的流畅。

  • 展示性能分析图和程序中消耗最大的函数

  • 展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路 (2分)

    if (poker_Lib.Count() != 0)
                {
                    this.Card_tmp = new poker();
                    Card_tmp = poker_Lib.Pop();
                    this.pictureBox2.Image = Image.FromFile(@"..\..\..\Resources\" + Card_tmp.getPokerImg());
                    this.pictureBox2.Refresh();
                    this.textBox1.Text = Convert.ToString(poker_Lib.Count());
                    this.textBox1.Refresh();
                    Thread.Sleep(1000);
                    if (this.poker_judge() == 2)
                    {
                        poker_tmp.Push(Card_tmp);
                        this.pictureBox2.Image = Image.FromFile(@"..\..\..\Resources\beimain2.jpg");
                        this.pictureBox2.Refresh();
                        this.pictureBox3.Image = Image.FromFile(@"..\..\..\Resources\" + Card_tmp.getPokerImg());
                        this.pictureBox3.Refresh();
                        this.textBox2.Text = Convert.ToString(poker_tmp.Count());
                        this.textBox2.Refresh();
                        Thread.Sleep(500);
                    }
                    else if (this.poker_judge() == 1)
                    {
                        poker_tmp.Push(Card_tmp);
                        if (whichone % 2 == 1)
                        {
                            Licensing_poker(P1);
                            this.P1_text_S.Text = Convert.ToString(this.P1.S_num());
                            this.P1_text_S.Refresh();
                            this.P1_text_H.Text = Convert.ToString(this.P1.H_num());
                            this.P1_text_H.Refresh();
                            this.P1_text_D.Text = Convert.ToString(this.P1.D_num());
                            this.P1_text_D.Refresh();
                            this.P1_text_C.Text = Convert.ToString(this.P1.C_num());
                            this.P1_text_C.Refresh();
                        }
                        else if (whichone % 2 == 0)
                        {
                            Licensing_poker(P2);
                            this.P2_text_S.Text = Convert.ToString(this.P2.S_num());
                            this.P2_text_S.Refresh();
                            this.P2_text_H.Text = Convert.ToString(this.P2.H_num());
                            this.P2_text_H.Refresh();
                            this.P2_text_D.Text = Convert.ToString(this.P2.D_num());
                            this.P2_text_D.Refresh();
                            this.P2_text_C.Text = Convert.ToString(this.P2.C_num());
                            this.P2_text_C.Refresh();
                        }
                    }
                }
    private void Double_war_Load(object sender, EventArgs e)
    {
        this.P1 = new Player();
        this.P2 = new Player();
        this.whichone = 0;
        this.Card_tmp = new poker();
        this.randint = new Random();
        this.poker_Lib = new Stack<poker>();
        this.poker_tmp = new Stack<poker>();
        this.create_card();
        this.poker_shuffle();
        this.push_poker();		
    }
    注释:Load函数在窗口打开是会自动运行,初始化函数生成卡牌用来检测卡牌生成与打乱是否正确,且检测点击摸牌是否可行,以及卡牌花色相同时收牌操作是否无误。
    

[2.3.2]贴出Github的代码签入记录,合理记录commit信息。(1分)

1635054260699

1635054288426

[2.3.3]遇到的代码模块异常或结对困难及解决方法。(4分)

困难描述(1分)
第一次使用C#winform窗口设计,感觉这个耦合性较强,在我增加窗口功能时很容易导致整个程序崩溃界面运行失败,调试程序也无法进行,一开始对于界面窗口画面变化也是没有头绪。对于网络接口这个部分,也是第一次接触,对于自己来说是非常困难的,对AI算法的实现也是非常欠缺。

解决过程(2分)
对于耦合性较强导致代码崩溃我没有找到解决办法,但是我也运行不了,也不可以进行调试,我只能回退到没有出现bug的时候。对于网络接口的使用我通过搜索引擎编程,很可惜我没有实现出来,窗口画面变化得到解决,对于AI算法的设计,我没有很好的实现出来,最后为了让我的人机对战很好的进行,最后将代码简化。

有何收获(1分)
在互联网时代,可以面向搜索引擎编程是很幸运的,哪里不会点哪里,一晚上可以速成一门编程语言(虽然只是一些基础语法),一晚上可以入门一个窗口设计,学会窗口设计,画面切换,可以完成简单的页面。

[2.3.4]评价你的队友。(2分)
值得学习的地方

评价胡驰:胡驰小兄弟还是很愿意学习的,学习能力很强,效率很高,分配的工作也是能够很好的完成。

需要改进的地方

嘿嘿,最好就是能不要这么摆。

值得学习的地方
评价李霆政:我自己是个不怎么愿意写代码的人,因此这次作业的绝大多数代码都是我的队友李霆政哥哥做的,这种code能力是我不具备的。而且他天资聪颖,敏锐过人拥有较强的学习能力和学习积极性,我觉得这种勤奋对于我来说也是极为重要的,非常感谢我的霆政哥哥能够担起项目的重任。

需要改进的地方
还是得多多使用原型设计工具,用PS画界面图是我有点没想到的,不过也是一种不一样的体验。还有就是代码实现设计方案的能力还是有待提高。

[2.3.5]提供此次结对作业的PSP和学习进度条(每周追加),示例如下(2分)

  • PSP表
PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 60 20
· Estimate · 估计这个任务需要多少时间 20 30
Development 开发
· Analysis · 需求分析 (包括学习新技术) 600 534
· Design Spec · 生成设计文档 120 40
· Design Review · 设计复审 30 60
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 15
· Design · 具体设计 360 60
· Coding · 具体编码 1200 1534
· Code Review · 代码复审 90 19
· Test · 测试(自我测试,修改代码,提交修改) 60 80
Reporting 报告
· Test Repor · 测试报告 150 30
· Size Measurement · 计算工作量 25 15
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 60 90
· 合计 2435 2527

李霆政:

第N周 新增代码(行) 累计代码(行) 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
1 0 0 10 10 了解人工智能算法、深度学习的入门进行
2 1532 1532 25 35 速成C#(基础语法)、winform、学会了原型设计、简单窗口实现

胡驰:

第N周 新增代码(行) 累计代码(行) 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
1 0 0 16 16 学习了HTML,CSS
2 0 0 9 25 学习熟悉了JS
3 0 300 7 32 自己的设计能力得到了提高,也熟悉了自己的项目
4 0 300 12 48 学习了博弈树,二分的AI算法,思考了如何才能让人机对战更有难度,简化游戏设计模型,研究游戏策略
5 300 600 10 58 熟悉并使用原型设计工具,让自己前端界面得到美化

三、心得(5分)

李霆政:
由于我们一开始分工没有做好,我想要进行AI算法设计,学一学人工智能这方面知识,但是后期由于我们还没有开始实现游戏界面,以及一些游戏逻辑的编写,我听同学C#winform是可视化操作的界面设计,比较简单就是有点丑(可能也不是有点),但是时间紧我没有什么时间考虑丑不丑的问题,想短时间实现出来这个游戏的界面以及基本逻辑。我非常感叹的就是现在这个时代我们可以哪里不会点哪里,可以很快的学习一门编程语言,然后学习所需要使用的框架,马上应用于实践,可惜的是时间没有安排没有做好网络接口,刚入门就结束,这个功能还是会在之后进行实现因为是一次很好的学习,这次这个界面是真的丑。

胡驰:
在做项目前,应该反复阅读项目本身的要求,如果此项目在市面上有原型,可以先使用一下,有个大致的想法,譬如这次猪尾巴的游戏,游戏机switch上就一个一模一样的,我也是在B站上查看了相关视频,更清晰的清楚了这个游戏的规则及玩法(实在是不想去阅读文字规则或者看图片,还是视频来的舒服),这让我对这个游戏有了一个更加直观具体的认识,在借用室友的switch玩了几局后,也慢慢的有了对这个游戏的一个大致感觉,冥冥之中知道想赢应该用怎样的策略,然后设计算法实现人机对战时,就将这个已经想好的策略具体化,让其具有可实施性,学习不是一蹴而就的,也应该一步一个脚印,慢慢解决途中遇到的问题,而非还没开始进行项目的实现就臆想怎样难怎么办,而应该面对这些实际问题时尽可能拆分成几小块逐一击破。前沿技术的学习和流行软件的使用也是我们应该时时刻刻铭记在心的,尽量避免用一些老方法来解决遇到的问题,这样不仅会使效率降低,而且做出来的东西也不会符合期望。

posted @ 2021-10-24 12:11  见贤思琪  阅读(43)  评论(0编辑  收藏  举报