结对编程作业

Github项目
李晓芳博客
江舒颖博客

姓名 具体分工
李晓芳 原型设计,游戏逻辑,AI算法
江舒颖 原型设计,前端界面,接口连接

一、原型设计

原型作品

设计说明

  • 设计逻辑:
    设计逻辑

  • 开发工具: 墨刀(MockingBot)

  • 原型介绍:

进入小程序将会进入启动页,点击进入游戏会显示选择游戏模式的界面

选择人机模式或者人人模式将不需要登陆,可直接进行游戏(以下以此为人人对战页面、人人对战带有托管页面、人机对战页面)

选择在线模式时,如果未登录,将会需要登录,登录后可自己创建房间或者加入其他人已经组好的对局,如果已经登陆则可以直接查询对局

游戏结束时将会有一个结束页面显示输赢情况,之后会有弹窗让你选择继续下一局返回主页面或者退出(以下依次为结束页面、退出登录页面)

遇到的困难及解决方法

  • 困难描述: 原型的保真度选择问题。低保真原型设计耗时短,易于理解,便于修改。高保真原型交互性强,有利于后续原型的实现,对团队协作更为友好,但设计耗时长,修改成本高。
  • 解决过程: 综合考虑高保真度和低保真度的优缺点,最理想的解决方案是:前期采用低保真原型,基本功能实现的中期采用高保真原型。再考虑到了时间的因素,我们制定了最后的计划:在结对编程的第一周时间内,尽可能地完善原型设计,核心关键部分必须制作高保真的原型,其他部分至少做出低保真原型。
  • 收获: 通过所见即所得的制作原型的形式,我们对页面跳转的交互设计方法有了更为深刻的理解。同时,在实际选择原型保真度的过程中,我们两人进行得到了初步的磨合,这为后期的原型设计实现带来方便。

二、原型设计实现

代码实现思路

  • 网络接口的使用
  1. 登录接口一开始使用 'content-type':'application/json'一直显示参数错误,后来查了一些资料把它改成了'content-type': 'application/x-www-form-urlencoded'就可以成功接入了。
  2. 除了登录接口,所以接口都需要带'Authorization':token
  3. 一些接口存在频繁使用,对于这个问题,我将其封装为函数,减少代码的冗余度
/*获取上步操作*/
getLast : function(e){
  const token=wx.getStorageSync("token");
  wx.request({
    url: 'http://172.17.173.97:9000/api/game/'+e+'/last',
    data: {},
    header: {'content-type':'application/json',
    'Authorization':token},
    method: 'GET',
    success: (res) => {
     console.log(res.data)
     if(res.data.code==200){
       wx.redirectTo({
         url: '/pages/RandRgames/RandRgames'
       });    
     }
    },
    fail: () => {},
    complete: () => {}
  });
 }
  • 代码组织与内部实现设计

    • 生命周期函数
      onLoad 可获取来自其它页面的数据
      onShow 实现一进入页面就要开始的操作,例如页面初始化
    • 页面渲染函数
      imageUrl 实现不同牌的图片转换
      changeview 实现托管页面的改变
      iflook 实现卡组(即记牌功能)的显示与隐藏
    • 对局函数
      clickscard、clickhcard、clickccard、clickdcard 分别对应Player1对于四种花色牌的响应
      clickscard1、clickhcard1、clickccard1、clickdcard1 分别对应Player2对于四种花色牌的响应
      Shuffle 洗牌操作
      SetCard 设置卡组初值
      SelectGroup 卡组抽牌
      OpHand 处理手牌
      SelectHand 手牌出牌
      IsEat 吃牌判断
      EatHand 手牌吃牌
      IsWinner 胜利者判断
      Update 更新页面数据
  • 说明算法的关键与关键实现部分流程图
    人人对战、人机对战、在线对战都以人人对战的游戏逻辑为基础展开。而游戏逻辑算法的关键在于手牌出牌、卡组抽牌这两个地方,在玩家完成操作后,更新手牌、卡组和放置区。
    关键实现部分流程图

  • 贴出重要的/有价值的代码片段,并解释
    是对人人对战游戏逻辑的基本实现,首先是洗牌,之后等待点击事件的发生,根据玩家选择,进行卡组抽牌、手牌出牌不同的操作。值得注意的是,卡组抽牌、手牌出牌需要更新手牌、卡组和放置区的卡牌,并对不同花色进行归类。

	Shuffle: function(){                     //  洗牌
    var that = this;
    var times = 5;                         //  默认洗牌5次
    for(var i = 0; i < times; i++){
      for(var j = 0; j < 52; j++) {
        var rvalue = Math.round(Math.random()*50+1);  //  随机生成1~51的一个整数
        var tempcard = that.data.onecard[j];
        that.data.onecard[j] = that.data.onecard[rvalue];
        that.data.onecard[rvalue] = tempcard;
      }
    }
  },
  SelectGroup: function(e){                //  卡组抽牌
    var that = this;
    that.data.placement_card[that.data.placement_num++] = that.data.group_card[that.data.group_num--];
    if(this.IsEat() == true){
      this.EatHand();
    }
    this.Update();
    if(that.data.group_num == -1){         //  卡组为空,结束对局
      that.data.finished = true;
      this.IsWinner();
    }
  },
  OpHand: function(e){                     //  处理手牌
    var that = this;
    var suit = that.data.card.substring(0, 1);
    var i = parseInt(that.data.turn);
    that.data.player[i].total++;
    switch (suit) {                         //  对应花色
      case "S":                             //  黑桃S
        that.data.player[i].hand[0][that.data.player[i].num[0]] =  that.data.card;
        that.data.player[i].num[0]++;
        break;
      case "H":                             //  红桃H 
        that.data.player[i].hand[1][that.data.player[i].num[1]] =  that.data.card;
        that.data.player[i].num[1]++;
        break;
      case "C":                             //  梅花C
        that.data.player[i].hand[2][that.data.player[i].num[2]] =  that.data.card;
        that.data.player[i].num[2]++;
        break;
      case "D":                             //  方块D
        that.data.player[i].hand[3][that.data.player[i].num[3]] =  that.data.card;
        that.data.player[i].num[3]++;
        break;
    }
  },  
  SelectHand: function(e){                  //  手牌出牌
    var that = this;
    var suit = that.data.card.substring(0, 1);
    var i = parseInt(that.data.turn);
    if(that.data.player[i].total != 0){
      switch(suit) {                      //  对应花色
        case "S": //黑桃S
          if(that.data.player[i].num[0] == 0){
            break;
          }
          that.data.player[i].total--;
          that.data.placement_card[that.data.placement_num++] = that.data.player[i].hand[0][--that.data.player[i].num[0]];
          break;
        case "H": //红桃H
          if(that.data.player[i].num[1] == 0){
            break;
          }
          that.data.player[i].total--;
          that.data.placement_card[that.data.placement_num++] = that.data.player[i].hand[1][--that.data.player[i].num[1]];
          break;
        case "C": //梅花C
          if(that.data.player[i].num[2] == 0){
            break;
          }
          that.data.player[i].total--;
          that.data.placement_card[that.data.placement_num++] = that.data.player[i].hand[2][--that.data.player[i].num[2]];
          break;
        case "D": //方块D
          if(that.data.player[i].num[3] == 0){
            break;
          }
          that.data.player[i].total--;
          that.data.placement_card[that.data.placement_num++] = that.data.player[i].hand[3][--that.data.player[i].num[3]];
          break;
      }
      if(this.IsEat() == true){
        this.EatHand();
      }
    }
    this.Update();
  },
  IsEat: function(e){//  吃牌判断
    var that = this;
    if(that.data.placement_num <= 1){
      return false;
    }
    var str1 = that.data.placement_card[that.data.placement_num-1].substring(0, 1);
    var str2 = that.data.placement_card[that.data.placement_num-2].substring(0, 1);
    if(str1 == str2){  //  花色相同
      return true;
    }
    return false;
  },
  EatHand: function(e){//  手牌吃牌
    var that = this;
    for(var i = 0; i < that.data.placement_num; i++){
      that.data.card = that.data.placement_card[i];
      this.OpHand();
    }
    that.data.placement_num = 0;
  },
  IsWinner: function(e){//  胜利者判断
    var that = this;
    if(that.data.player[0].total < that.data.player[1].total){
      that.data.winner = "0";
    }else{
      that.data.winner = "1";
    }
    this.setData({
      image_url0:'https://i.loli.net/2021/10/24/hFNM9dbDB1wCnak.png'
    })
     wx.navigateTo({
            url: '/pages/over/over?id=that.data.winner'
          });
  },

  • 性能分析与改进
    性能上,小程序的图片加载较慢,影响了对游戏玩法的体验感。通过去除冗余的界面,完善了界面逻辑,在这方面有了一定的改进。
    游戏逻辑方面,各个类的封装性做得不是很好,出现过相互扰乱的问题。游戏的反应不是很灵敏。

  • 描述改进的思路
    兼并人人对战、人机对战、在线对战公用的函数;不需要页面响应的数据不放在data中初始化;避免频繁触发的事件重度内存操作。尽可能减少小程序对运行内存的消耗。
    去除不必要的冗余界面,完善界面交互的逻辑。

  • 展示性能分析图和程序中消耗最大的函数
    性能分析图
    消耗最大的函数应该是启动函数。

  • 展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路
    测试的是生命周期函数--监听页面加载onLoad()。
    比对游戏参数在接口的返回和本地的记录是否一致,不一致的话查看出错的地方。信息通过 console.log()输出。

    wx.request({
      url: 'http://172.17.173.97:9000/api/game/?uuid',
      header: {'content-type':'application/json',
      'Authorization':token},
      method: 'GET',
      success: (res) => {
        //  核验信息是否一致
        console.log(res.data);
        console.log(that.data.player[0].hand);
        console.log(that.data.player[1].hand);
        if(res.data.winner!=that.data.winner){
          console.log("error");
        }
      },
      fail: () => {},
      complete: () => {}
    });

贴出Github的代码签入记录,合理记录commit信息

Github代码签入记录

遇到的代码模块异常或结对困难及解决方法

  • 困难描述: 小程序开发过程中,出现白屏问题。可能是运行内存过大、接口API超时。
  • 解决过程: 首先,减少小程序对运行内存的消耗。一方面,不需要页面响应的数据不放在data中初始化。另一方面,避免频繁触发的事件重度内存操作,比如上拉加载,下拉刷新时间增加节流。其次是,监督接口API是否超时。
  • 收获: 最大的收获是一个bug满满的小程序。但其实在制作小程序的过程中,我们已经解决了诸如小程序出现白屏等很多问题,这是一个成长的过程。

评价你的队友

李晓芳:

  • 队友值得学习的地方:
  1. 会主动督促项目进行,常常是push我学习和打代码。
  2. 有不错的前端编程基础,协作起来轻松愉快。
  3. 编程有一些需要交接的地方,例如参数传递、文件做出了改动,总是说得很清楚。
  • 队友需要改进的地方:
  1. 应该也是ddl驱动型队友,虽然我们一起扛ddl,但是交作业的时候太焦头烂额了。
  2. 注释跟空格强迫症看了有一点难受,希望能做改进。

江舒颖:

  • 队友值得学习的地方:
  1. 在做事情前会先把思路理清,便于更好地展开工作。
  2. 善于把握大方向,会以整体为重,不会因小失大。
  3. 当不能把每个地方都做得很好时,能够比较准确地抓住比较重要的地方先做。
  4. 代码规范性很好,习惯于做注释,可读性强
  • 队友需要改进的地方:
  1. ddl驱动型队友,觉得可以更早一点开始完成作业,可以完成得更好。
  2. 沟通仍需加强,关于一些功能、接口参数在我们俩之间存在理解偏差,导致在编写代码过程中有些工作会突然没法进行。

PSP和学习进度条

PSP

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

学习进度条

第N周 新增代码(行) 累计代码(行) 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
1 0 0 5 5 讨论作业要求,并确定要以哪种形式开发以及分工,查找了关于游戏开发的相关资料
2 300 300 9 14 进行了游戏功能的讨论,确定页面跳转的逻辑,共同进行原型设计,学习了微信小程序的基本语法
3 1100 1400 18 32 实现了部分页面的构建;人人对战界面的基本实现
4 800 4200 18 50 实现了页面的动态渲染以及接口的连接,将页面之间的转换按逻辑连接; 完成其他对战模式,全部的游戏逻辑

三、心得

  • 李晓芳:
  1. 原型制作是一个比较好玩的过程。一方面,所见即所得的制作方式比较轻松,另一方面,在讨论原型的过程中,我和队友进行了头脑风暴,达成一致的想法都是我们认为好看、有趣、好玩的。
  2. 每次软工作业都能学到新的东西,虽然学习的质量跟深度有待商榷,但确确实实为我推开很多扇大门。
  3. 团队之间的沟通十分重要。虽然我和队友经常展开讨论,并且当得出一致结论,但在讨论之后,仍然会对先前讨论的问题有疑惑,这是由于沟通并不十分有效。我们需要探寻更有效的沟通方式。
  • 江舒颖:
  1. 前期虽然查资料会花费很大一部分时间,因此没有编写代码,看起来像是工作毫无进展。但是这个时间一定要花,在完成一个自己没接触过的东西之前,需要去了解它,想清楚到底需要学什么东西,做好准备工作才能更有利于后期的进行。
  2. 和队友的沟通很重要,关于新事物的理解每个人都不同,所以需要和队友讨论确定一个正确的方向。
  3. 分工明确很重要,明确的分工才能明确地知道自己要做什么,各司其职,就可以达到1+1>2的效果。
posted @ 2021-10-24 22:38  尼古拉斯宝莉  阅读(45)  评论(0编辑  收藏  举报