2023软工K班结对编程任务

GitHub仓库连接:开心投投乐仓库链接
bilibili视频连接:开心投投乐视频演示链接
小程序名称:开心投投乐
小程序现已上线!微信搜索“开心投投乐”即可游玩

一、结对探索

1.1队伍基本信息

结对编号:2
队伍名称:AI呦队

学号姓名作业博客连接具体分工
102101428陈宇尘原型制作、前端逻辑编写、实现本地对战和人机对战、性能分析、单元测试、博客编写、视频制作
102101430林文光小程序页面代码搭建、云服务器搭建与连接、在线对战功能编写、彩蛋的设计与制作 、GitHub代码管理

1.2结对过程

都在同个宿舍同个班,也有各自擅长的技能,就一起组队了

1.3非摆拍的两人在讨论设计或结对编程过程的照片

在这里插入图片描述

二、原型设计

2.1原型工具的选择

本次作业我们使用了墨刀作为原型工具。主要是看上届的作业也主要是用这个软件设计原型的;在网上看了下教程发现使用起来也很简单,可以比较轻松的画出好看的原型界面;并且在墨刀设计的组件还可以直接导出作为前端的素材,非常的灵活便捷;以及还是免费加国产软件,最后就决定用墨刀进行原型设计了

2.2 遇到的困难与解决办法

1.在设计原型的时候最大的困难就是找合适的UI了,在网上找了很多图片都找不到喜欢的。还好最后找到了iconfont,incons8等免费使用的UI网站,在里面找到了很多精美的UI图像,为设计原型提供了很大的便捷和选择。
2.还有一些难点就是页面的整体布局,去微信小程序找了很多类似的小程序学习他们的布局,有了一定概念之后就开始自己画了第一份原型,后来因为找到了很多新的UI就又把之前设计的原型全部推翻再重新设计一遍,算是比较满意了。
3.其次就是对于AI算法的设计,决定用最大得分期望来表示AI的选择依据,但是不知道该如何求每种选定方案收获的期望。最终决定直接暴力遍历每一种情况就好了,于是采取了递归算法。因为之前不怎么用js编程,所以递归跑起来有很多bug,在疯狂复制粘贴报错后终于把算法实现了。最终实现的效果还是比较不错的。
4.最后就是在线匹配的逻辑一直理不清,例如玩家加入房间的顺序出错,显示的玩家头像混乱,截取服务器传输和接收的数据错误。折腾了半天最终在共同的讨论以及查询网上资料后总算全部解决。
主要收获就是提高了自己查找资源的能力和对界面的布局能力还有算法的设计以及找bug的能力。

2.3 原型作品链接

开心投投乐原型链接

2.4 原型界面图片展示

  • 本次作业是在中秋节左右,以及中秋博饼也和骰子有关,因此我们在界面中加入了大量中秋元素,例如月亮,月饼,玉兔等等;同时一些界面的布局也富有内涵,具体在下面原型图片展示中介绍
  • 同时我们在游戏中加入了班级特色彩蛋,例如在人机对战的AI头像中使用了班级熟悉的经典人物作为头像,并且点击头像会有相应富有各自特点的对话框弹出

首页

在这里插入图片描述

  • 首页分别有三大模块,本地对战,人机对战和在线对战,点击图片进入对应模式
  • 首页显示玩家用于在线对战的筹码数
  • 首页左上方是玩家信息图标,点击后进入玩家信息页面
  • 首页底部点击规则即可进入规则页面

游戏规则

在这里插入图片描述

本地对战设置

在这里插入图片描述

  • 此次可设置对局人数,初始筹码,对局局数信息

本地对战游戏界面

在这里插入图片描述

  • 界面主要是中秋博饼风格,非常的欢快。界面下方有狐狸兔子桌子等元素,表示它们在一起快乐地玩骰子。另一角度也隐喻着赌徒就想牌桌上的兔子,任由庄家老狐狸摆布
  • 界面最顶部显示当前轮数和当前局数
  • 界面上方为投掷框,为提高界面利用率和简洁性,我们将投掷区和选定区合二为一,玩家投掷后可点击需要选定的骰子,骰子上会显示锁定的图标,被锁定的骰子在下次投掷时将被锁住不会改变点数
  • 界面再下方是玩家信息预览框,分别显示所有玩家当前的信息,包括玩家头像,当前点数,当前牌型,骰子得分,当前筹码,滑动预览框可以看到未显示在页面中的其他玩家信息
  • 界面中部是当前投掷的骰子数据显示栏,有本次投掷的骰子获得的对应牌型,积分以及游戏目前的总倍数
  • 点击红色的骰子图标可以投掷骰子,加倍和跳过对应各自的功能
  • 点击酷炫的小机器人即可托管,会有强大的AI帮助当前玩家代打,当前玩家在自己的回合再次点击小机器人可以取消托管
创新之处:
  • 点击确定后兔子图标会往左移动再从界面右侧回来,表示切换到下一位玩家,增加交互性
  • 点击兔子和狐狸图标可以实现互动

人机对战设置界面

在这里插入图片描述

  • 顶部可以设置游戏难度,分为简单,中等,困难三种难度,玩家可自行选择;选择不同的难度进入游戏后会有对应的AI头像
  • 在人机对战中可以设置玩家自己的头像
  • 其余和本地对战一致

人机对战界面

在这里插入图片描述

  • 界面最顶部显示当前轮数和当前局数
  • 界面上方为投掷框,为提高界面利用率和简洁性,我们将投掷区和选定区合二为一,玩家投掷后可点击需要选定的骰子,骰子上会显示锁定的图标,被锁定的骰子在下次投掷时将被锁住不会改变点数
  • 界面再下方是玩家信息预览框,分别显示所有玩家当前的信息,包括玩家头像,当前点数,当前牌型,骰子得分,当前筹码,滑动预览框可以看到未显示在页面中的其他玩家信息
  • 界面中部是当前投掷的骰子数据显示栏,有本次投掷的骰子获得的对应牌型,积分以及游戏目前的总倍数
  • 点击红色的骰子图标可以投掷骰子,加倍和跳过对应各自的功能

彩蛋和创新之处:

  • 把AI的头像设置为班级知名头像作为班级特色,作为游戏内的彩蛋;选择不同的AI难度对应的AI头像也不同
  • 点击AI头像可以有互动彩蛋,产生有各自特点的对话框

在线对战设置界面

在这里插入图片描述

  • 点击头像可以设置游戏中显示的玩家头像,可以自动导入微信头像或选择本地相册
  • 点击昵称可以设置游戏中显示的玩家名,可以自动导入微信名或自行命名
  • 设置好对局人数和对局局数后,服务器会自动让玩家加入相同设置的房间频道,可以与其他客户端的玩家一起对战!

在线对战界面

在这里插入图片描述

  • 进入房间后需等待房间人满,人满后自动开始游戏
  • 按照加入房间的顺序自动设置每个玩家的投掷顺序,前面所有玩家投掷结束和才可以进行投掷

三、编程实现

3.1 网络接口的使用

微信原生接口api:

  • wx.redirectTo:实现各个界面间的跳转。
  • wx.showModal:显示模态对话框,与用户交互。
  • wx.request:发起 HTTPS 网络请求,实现与网络api的连接。
  • bindchooseavatar:头像昵称填写,现版本微信已经不支持通过wx.getUserInfo获取用户头像昵称,由用户自行填写。
    其他接口api:
  • console.log: 神中神,js调试必备。
  • wx.goEasy:利用goEasy搭建云服务器实现在线Websocket通讯,是实现在线对战的核心api。
  • https://api.likepoems.com/img/pe/:这是一个随机图片api,由于目前微信小程序已经不让收集用户头像,在线对战的过程中你只能看到自己的头像,其他人的头像都是从这个接口生成的随机头像。

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

data定义和dice类:

在这里插入图片描述

前端文件:

在这里插入图片描述

在这里插入图片描述

3.3 说明算法的关键与关键实现部分流程图

  • 实现人机自动选择最优选定方案
//计算最优锁定方法
  calculateBestLocks:function(points,locks) {
    let locksPossible=[];//存储所有可能的骰子锁定数组
    let max_score=0;//存储最大的分数期望
    let best_locks=[];//存储最优锁定方法
    let res=[];
    //定义一个深度优先搜索,查找每种锁定数组,然后存储到locksPossible中
    const lockDFS=(cur_points,cur_locks,cur_index)=>{
      let locks_copy=[...cur_locks];//复制副本,用于递归,防止原数组被篡改
      if(cur_index==cur_points.length){
        locksPossible.push(locks_copy);//如果当前的下标超过数组长度,说明此次递归到尽头,则保存当前的锁定数组
        return;
      }
      if(!locks_copy[cur_index]){//如果当前骰子没被锁定,则骰子被锁定和没被锁定的情况都要遍历一次
        locks_copy[cur_index]=false;
        lockDFS(cur_points,locks_copy,cur_index+1);
        locks_copy[cur_index]=true;
        lockDFS(cur_points,locks_copy,cur_index+1);
      }
      else{//被锁定则没有别的选择,直接看下一个骰子
        lockDFS(cur_points,locks_copy,cur_index+1);
      }
    }
    lockDFS(points,locks,0);//执行递归函数,获得所有可能的锁定数组
    //遍历所有可能的锁定数组,计算每种锁定情况对应的分数期望
    for(let i=0;i<locksPossible.length;i++){
      let total_score=0;//存储当前锁定方法下每种投掷结果获得分数的综合
      let count=0;//存储当前锁定方法下共有每种投掷结果
      //上面二者相除及为获得分数的平均值或期望
      //遍历接下来投掷可能出现的所有点数结果
      const pointsDFS=(cur_points,cur_locks,cur_index)=>{//定义深度优先搜索函数
        let points_copy=[...cur_points];
        if(cur_index==points_copy.length){//当前递归下标等于数组长度,当此递归结束
          total_score+=this.getPointType(points_copy).sum;//直接调用之前定义的计算骰子分数的函数
          count++;
          return;
        }
        if(!cur_locks[cur_index]){//如果骰子没被锁定,则下次投掷可能出现1-6的点数,每种都要递归一次
          for(let j=1;j<=6;j++){
            points_copy[cur_index]=j;
            pointsDFS(points_copy,cur_locks,cur_index+1);
          }
        }
        else{
          pointsDFS(points_copy,cur_locks,cur_index+1);
        }
      }
      pointsDFS(points,locksPossible[i],0);//根据当前锁定数组计算期望
      if(count>0&&total_score/count>max_score){
        max_score=total_score/count;
        best_locks=locksPossible[i];
      }
    }
    res.push(best_locks);
    res.push(max_score);
    return res;
  },
  • 人机自动加倍
//人机自动加倍
  AIAddMul:function(){
    var dices=this.data.dices;
    var player_points=dices[0].points,AI_points=dices[1].points;
    var player_locks=dices[0].locks,AI_locks=dices[1].locks;
    var player_score=this.calculateBestLocks(player_points,player_locks)[1];//计算玩家最高得分期望
    var AI_score=this.calculateBestLocks(AI_points,AI_locks)[1];//计算人机最高得分期望
    if(5<=AI_score-player_score&&AI_score-player_score<10){
    //比较二者得分差,分差越大加倍越多
      this.setData({
        total_mul:this.data.total_mul*2
      });
    }
    else if(10<=AI_score-player_score&&AI_score-player_score<15){
      this.setData({
        total_mul:this.data.total_mul*3
      });
    }
    else if(15<=AI_score-player_score){
      this.setData({
        total_mul:this.data.total_mul*4
      });
    }
  },
  • 人机自动控制
AIController:function(){
    if(this.data.canAIthrow&&!this.isAllLock(this.data.dices[this.data.current_player].locks)){
      this.startShake();//直接模拟人执行投骰子函数
      this.playAnimation();
      var dices,points,locks,lockdices=this.data.lockdices,lockdice=[];
      setTimeout(()=>{//因为开始投掷到得出点数需要1s的动画,所以过1s后获取骰子信息才有效
        dices=this.data.dices;
        points=dices[this.data.current_player].points;
        locks=dices[this.data.current_player].locks;
        locks=this.calculateBestLocks(points,locks)[0];
        dices[this.data.current_player].locks=locks;
        for(let i=0;i<5;i++){//将被锁的骰子放入锁定区中
          if(locks[i]){
            lockdice.push(points[i]);
          }
        }
        lockdices[this.data.current_player]=lockdice;
      },1000);
      //确定骰子点数和选定好骰子
      setTimeout(()=>{
        this.setData({
          dices:dices,
          lockdices:lockdices,
          canAIthrow:false
        });
        this.AIAddMul()},1000);
    }
    //过一段时间后再确定,之后轮到玩家回合
    setTimeout(()=>{
      this.AIcommit();
    },2500)
  },
  • 人机自动选定最佳选定方案函数流程图
    在这里插入图片描述
    在这里插入图片描述

​ 3.4 贴出重要的/有价值的代码片段并解释

  • 游戏界面初始化
initGame:function(){//初始化游戏,设置玩家人数
    var players=[];
    var dices=[];
    var lockdices=[];
    this.setData({//将全局数据保存的玩家人数和游戏局数先赋值给data
      player_num:app.globalData.local_player_num,
      total_rounds:app.globalData.local_game_rounds
    });
    for(var i=0;i<this.data.player_num;i++){
      players.push({id:"",unique:"",money:app.globalData.local_player_money,score:0,type:'',canthrow:true})
      dices.push({paths:["/static/images/pvp_game/dice0.png","/static/images/pvp_game/dice0.png","/static/images/pvp_game/dice0.png","/static/images/pvp_game/dice0.png","/static/images/pvp_game/dice0.png"],
      timers:[null,null,null,null,null],
      locks:[false,false,false,false,false],
      points:[0,0,0,0,0]},)
      lockdices.push([]);
    }
    this.setData({
      players:players,
      dices:dices,
      lockdices:lockdices
    });
    this.playRabbit();
  },

  • 实现投掷骰子和骰子动画播放
//改变每个骰子的点数
  change_dice_i: function(i){
    var dices=this.data.dices;
    var dice=dices[this.data.current_player];
    var locks=dice.locks;
    //获取骰子锁信息,如果没被锁住才能投掷
    if(!locks[i]){
      var dice_num = [1, 2, 3, 4, 5, 6];
      var paths=dice.paths;
      var timers=dice.timers;
      var timer=timers[i];
      var points=dice.points;
      //生成一个计时器,每隔50ms切换一次骰子点数,持续1s
      timer = setInterval(() => {
        if (dice_num.length <= 0) {
          dice_num = [1, 2, 3, 4, 5, 6];
        } 
        else {
          var randnum = Math.floor(Math.random() * dice_num.length);
          paths[i]="/static/images/pvp_game/dice" + dice_num[randnum].toString() + ".png"//-----需改为项目中地址------
          points[i]=dice_num[randnum];
          dice.paths=paths;
          dice.points=points;
          dices[this.data.current_player]=dice;
          this.setData({
            dices:dices
          });
          dice_num.splice(randnum, 1);
        }
      }, 50);
      dice.timers=timers;
      dices[this.data.current_player]=dice;
      this.setData({
        dices:dices
      });
      //1s后清除计时器
      setTimeout(()=>{
        clearInterval(timer);
        this.setData({
          animationRunning:false
        });
      },1000)
    }
  },
  • 实现骰子的选定
//切换骰子的状态,被锁或没被锁,用于绑定在每个骰子图片上,点击一次就可以切换一次状态
  changeState:function(event){
    if(!this.data.players[this.data.current_player].canthrow){
      const i = event.currentTarget.dataset.params;//获取这是第几个骰子的数据
      var dices=this.data.dices;
      var lockdices=this.data.lockdices;
      var dice=dices[this.data.current_player];
      var locks=dice.locks;
      if(!locks[i]){
        locks[i]=true;//切换到与当前相反的状态
        dice.locks=locks;
        dices[this.data.current_player]=dice;
        lockdices[this.data.current_player].push(dice.points[i]);//将被锁定骰子加入锁定区中
      }
      this.setData({
        dices:dices,
        lockdices:lockdices
      });
    }
  },
  • 实现计算骰子的得分与牌型
//获取骰子的总积分和牌型
  getPointType:function(points){
    var point_count=[0,0,0,0,0,0,0];
    var diceClass=new this.diceClass(0,'无牌型');
    var points_sum=this.points_sum(points);
    //计算每种点数出现次数
    for(let i=0;i<5;i++){
      point_count[points[i]]++;
    }
    //获取最大的出现次数是多少和最小的出现次数是多少
    var max_count=Math.max(...point_count),min_count=this.getMinCount(point_count);
    if(max_count==2){//如果最大的出现次数是2,说明只有双对和无牌型两种情况
      let count=0;
      //统计出现了几次双对
      for(let i=1;i<=6;i++){
        if(point_count[i]==2) count++;
      }
      if(count==2){
        diceClass.sum=points_sum+10;
        diceClass.type='双对';
      }
      else{
        diceClass.sum=points_sum;
        diceClass.type='无牌型';
      }
    }
    else if(max_count==3){
      if(min_count==1){
        diceClass.sum=points_sum+10;
        diceClass.type='三连';
      }
      else{
        diceClass.sum=points_sum+20;
        diceClass.type='葫芦';
      }
    }
    else if(max_count==4){
      diceClass.sum=points_sum+40;
      diceClass.type='四连';
    }
    else if(max_count==5){
      diceClass.sum=points_sum+100;
      diceClass.type='五连';
    }
    ///如果以上情况都不是,说明每种骰子都只出现一次,则可能是顺子或无牌型
    if(max_count==1||(max_count==2&&diceClass.type=='无牌型')){//需要考虑等于2的情况是因为可能有1,1,2,3,4的情况,属于小顺子,
      //但是会被之前max_count等于2的情况下判断为无牌型,所以这里还要再判断一次
      let count=0,max_count2=0;
      //统计连续的点数序列有多长
      for(let i=1;i<=6;i++){
        if(point_count[i]>0){
          count++;
        }
        else{
          count=0;
        }
        if(count>max_count2) max_count2=count;
      }
      if(max_count2==4){
        diceClass.sum=points_sum+30;
        diceClass.type='小顺子';
      }
      else if(max_count2==5){
        diceClass.sum=points_sum+60;
        diceClass.type='大顺子';
      }
      else{
        diceClass.sum=points_sum;
        diceClass.type='无牌型';
      }
    }
    return diceClass;
  },
  • 实现玩家点击确定后可能触发的一系列事件
//确认骰子,跳过当前回合,同时需要把骰子数据恢复让下一个玩家投
  //当最后一名玩家按下确定按钮后轮数可能满以及局数也可能满,所以游戏进入新一轮,新一局以及判断游戏结束也在这判断
  commit: function() {
    if((!this.data.players[this.data.current_player].canthrow
      ||this.isAllLock(this.data.dices[this.data.current_player].locks))
      &&!this.data.isGameOver
      &&!this.data.players[this.data.current_player].is_AI){
      var point_type = '';
      var point_sum = 0;
      var turns = this.data.turns;
      var currrent_round=this.data.current_round;
      var current_player = this.data.current_player + 1;
      var players = this.data.players; // 创建 players 的副本进行操作
      var dices=this.data.dices;//创建dices副本
      var lockdices=this.data.lockdices;
      var total_mul=this.data.total_mul;
      if (current_player == this.data.player_num) {//如果下标超过人数,说明可以进行下一轮
        current_player = current_player % this.data.player_num;
        turns++;
        for (let j = 0; j < this.data.player_num; j++) {//把是否可以投掷状态恢复
          players[j].canthrow = true;
        }
      }
      if(turns>3){//按完后如果轮数大于3,则说明当局结束,进入下一轮
        turns=1;
        currrent_round++;
        this.checkout();
        for(let j=0;j<this.data.player_num;j++){//进入新一轮,重置玩家和骰子信息
          dices[j]={paths:["/static/images/pvp_game/dice0.png","/static/images/pvp_game/dice0.png","/static/images/pvp_game/dice0.png","/static/images/pvp_game/dice0.png","/static/images/pvp_game/dice0.png"],
          timers:[null,null,null,null,null],locks:[false,false,false,false,false],points:[0,0,0,0,0]};
          players[j].type='';players[j].score=0;total_mul=1;
          lockdices[j]=[];
        }
      }
      if(currrent_round>this.data.total_rounds){//如果当前局数超过总局数,游戏结束
        currrent_round=this.data.total_rounds;
        this.setData({
          isGameOver:true
        });
      }
      //更新当前玩家信息
      this.setData({
        players: players,
        dices:dices,
        lockdices:lockdices,
        point_type: '',point_sum: 0,
        turns: turns,
        total_mul:total_mul,can_mul:true,
        current_round:currrent_round,current_player: current_player
      });
      this.changePlayer();
      if(this.data.players[this.data.current_player].is_AI){
        setTimeout(()=>{
        this.setData({
          canAIthrow:true
        });
        this.AIController();},1000)//1s后开始转为人机控制
      }
    }
  },

​ 3.5 性能分析与改进

采用了微信小程序自带的真机调试进行性能分析,发现在页面切换时会有较大的耗时和内存消耗,经过分析进行如下改进:
  • 原先页面切换后会继续保存之前的页面,消耗内存,于是改为切换页面后就清除之前的页面,下次进来再重新加载,减少开销
<navigator class="back" url="/pages/home/home" open-type="redirect">
      <image src='atic/images/back.png'></image>
    </navigator>
  • 将一些网络图片改为项目内静态图片,减少请求网络图片带来的时间消耗
  • 将组件注入模式改为懒加载,减少进入界面时的工作量
  • 将一些需要多个界面都会用到的大文件移至全局缓存,避免重复加载
  • 优化resquest等函数的写法,避免回调地狱
  • 优化部分动画的运动逻辑
性能分析图:

在这里插入图片描述

​ 3.6 单元测试

  • 导入测试工具:
// 小程序工具集
$ npm i --save-dev miniprogram-simulate
// Jest测试框架
$ npm i --save-dev jest
  • 单元测试代码

```javascript
const pve = require('./pve_function.js');
describe('PVE Game', () => {
  describe('isAllLock', () => {
    it('should return true when all locks are true', () => {
      const locks = [true, true, true, true, true];
      const result = pve.isAllLock(locks);
      expect(result).toBe(true);
    });

    it('should return false when any lock is false', () => {
      const locks = [true, true, false, true, true];
      const result = pve.isAllLock(locks);
      expect(result).toBe(false);
    });
  });
  describe('getPointType', () => {
    it('should return correct result for no point type', () => {
      const points = [1, 2, 3, 5, 6];
      const result = pve.getPointType(points);
      expect(result.type).toBe('无牌型');
      expect(result.sum).toBe(17);
    });
  
    it('should return correct result for double pair', () => {
      const points = [1, 1, 2, 2, 6];
      const result = pve.getPointType(points);
      expect(result.type).toBe('双对');
      expect(result.sum).toBe(22);
    });
  
    it('should return correct result for three of a kind', () => {
      const points = [2, 2, 2, 4, 6];
      const result = pve.getPointType(points);
      expect(result.type).toBe('三连');
      expect(result.sum).toBe(26);
    });
  
    it('should return correct result for full house', () => {
      const points = [3, 3, 5, 5, 5];
      const result = pve.getPointType(points);
      expect(result.type).toBe('葫芦');
      expect(result.sum).toBe(41);
    });
  
    it('should return correct result for four of a kind', () => {
      const points = [4, 4, 4, 4, 6];
      const result = pve.getPointType(points);
      expect(result.type).toBe('四连');
      expect(result.sum).toBe(62);
    });
  
    it('should return correct result for five of a kind', () => {
      const points = [5, 5, 5, 5, 5];
      const result = pve.getPointType(points);
      expect(result.type).toBe('五连');
      expect(result.sum).toBe(125);
    });
  
    it('should return correct result for small straight', () => {
      const points = [1, 2, 3, 4, 6];
      const result = pve.getPointType(points);
      expect(result.type).toBe('小顺子');
      expect(result.sum).toBe(46);
    });
  
    it('should return correct result for large straight', () => {
      const points = [2, 3, 4, 5, 6];
      const result = pve.getPointType(points);
      expect(result.type).toBe('大顺子');
      expect(result.sum).toBe(80);
    });
  });
});

测试了getpoint和isAllLock这两个比较关键的函数,测试的函数全部覆盖且测试结果与预期结果一致

在这里插入图片描述

​ 3.7 贴出GitHub的代码签入记录,合理记录commit信息

四、总结反思

​ 4.1 本次任务的PSP表格

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划120120
· Estimate· 估计这个任务需要多少时间72006000
Development开发30002880
· Analysis· 需求分析 (包括学习新技术)24001920
· Design Spec· 生成设计文档180180
· Design Review· 设计复审120120
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)10090
· Design· 具体设计720600
· Coding· 具体编码18001440
· Code Review· 代码复审9060
· Test· 测试(自我测试,修改代码,提交修改)9060
Reporting报告240200
· Test Report· 测试报告6030
· Size Measurement· 计算工作量3030
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划6060
·· 合计72006000

4.2 学习进度条(每周追加)

  • 陈宇尘
第N周新增代码(行)累计代码(行)本周学习耗时(小时)累计学习耗时(小时)重要成长
11001002424学习墨刀的使用,设计好原型,学习微信小程序特性以及复习下html,css,js等一些知识,实现能点击按钮投掷骰子功能
25006003054实现了本地对战功能,学会app.js传递全局数据,将设置界面设置的参数同步到本地游戏中。学会用data保存玩家信息,基本实现玩家轮流投掷骰子和结算功能
3150021001872实现了人机对战功能,学会利用递归和settimeout计时器模拟人机操作的功能
210003100880对代码进行测试和调优,跟着学了点websocket
  • 林文光
第N周新增代码(行)累计代码(行)本周学习耗时(小时)累计学习耗时(小时)重要成长
1002020学习微信小程序的代码写法,重温css、JavaScript的语法,基本具备搭建小程序能力
2150015001636搭建了微信小程序页面,了解利用服务器上传文件信息,掌握部分微信api的使用方法
380023002056学习利用云服务器通讯,实现在线对战功能,掌握Promise的用法
420025001010设计彩蛋,提交GitHub,掌握上文所述的api的使用方法,了解微信小程序的测试调试方法。

​ 4.3 最初想象中的产品形态、原型设计作品、软件开发成果三者的差距如何?

产品形态和预想的差不多甚至超乎预期了一些,原型和真正的前端页面基本一致。最后也算实现基本实现了想要的功能,但是由于时间问题有一些有趣的想法来不及实现,例如在线对战中的托管功能。虽然我们已经在本地对战实现了这个功能,但是由于在线对战的机制比较繁琐,再在其中移植功能容易出现bug,外加最后几天被在线对战折磨的实在不想动了,所以最好就放弃了把本地对战的托管功能移植到在线对战的托管功能。
总体来说是很不错了,最终效果和最初构想基本一致甚至超出预期,之后会继续完善这个项目。

​ 4.4 评价你的队友

陈宇尘:
我和wg配合的还是十分默契的。每个人都能负责各自擅长的东西,自己的负责的内容做完后也会帮助对方一起解决问题。
这个项目一开始是完全没有头绪的。不知道到底能完成到什么样的程度,先在墨刀上随手画个大概的样子,没想到wg立刻就能根据这个写出一模一样的前端页面,瞬间信心大增。从此在界面的设计上开始放飞自我,不论原型设计的多么花里胡哨,相信我的队友一定能把这个界面实现出来的。我不太擅长写前端界面所以他能把这个全部做完实在是太可靠了。
还有就是在作业快要截至的时候在线匹配机制还是没有实现,本来我都想开摆不想实现这个功能了。但是他非常坚定地认为我们一定可以实现这个功能。于是开始疯狂学习在线匹配的知识,终于在最后一天把在线匹配做出来了。中途我们也一起讨论了很久,每次出bug他也总能一眼就看出问题所在,实在太C辣。
总的来说这次项目的实现我们每个人都发挥了很大作用,合作得非常完美。

林文光:
1.说实话我感觉有个人能帮自己一起开发还挺爽的,就像api接口一样,只需沟通好我想要什么,大概怎么做,剩下的具体算法可以甩手不管了。这描述似乎就像是一个连接脑机接口的GPT一样(我觉得这是很高的评价),你巴拉巴拉讲一大段,然后问他一句你懂我意思吧,他可以自己把任务细化然后去问GPT。我觉得真的是很棒的体验。
2.做出上述评价有一个根本前提,就如我开博文一开始结对过程中说的,结对队友是一个及时主动解决问题的人,而不是一个你要一直去push的对象。及时主动+积极沟通,我觉得这就是cyc身上值得大部分程序员(起码值得我)学习的闪光点。
3.其实我觉得结对过程就是各自发挥长处、优势互补的过程,指摘他人需要改进的地方之前要先考虑自己能不能弥补,能弥补的我就不把他当作问题。你看他对我的评价,他夸我的地方就是他还需要精进的地方,反过来也是同理。要说需要改进的地方就是**“饼“”画的不够大**,定的目标很小,总是觉得实现了这个功能就够了,结果被我硬加了很多功能。

​ 4.5 结对编程作业心得体会

陈宇尘:
终于把这次的作业做完了,实在太累了,感觉大脑快要死机了。但是最后把整个作业的所有部分做完后还是十分激动和有成就感的。
对这次作业的总结就是循序渐进吧,最开始要做这个作业的时候毫无头绪,从来没有用过微信小程序开发,虽然之前学过一点前端但是js部分还是不太会写。在网上找了找学习视频,看着一个又一个长达十几小时的视频陷入沉思。于是决定边做边学,先自己写了个能投掷骰子的动画,看着还是非常活灵活现的,但是只是固定动画还不能随机变点数。最后又花了一个晚上的时间配合js写了个投掷一个骰子的函数加上button。总算是迈出第一步了。最后是要投掷五个骰子,这个比较简单写个循环轮流投就完事了。最后是每个玩家可以轮流投掷骰子。这时候发现之前写的代码的重大问题,就是我只写了一个数组保存数组,但是每个玩家要有自己的骰子数组以及一大堆乱七八糟的数据要存储,又花了好几个小时重构了一下代码。经历了两天的奋战终于把本地对战实现了。
后来又要实现人机对战,有了前面的本地对战其实就是套皮就好了,把玩家改成人机控制别的都不变就够了。但是人机的函数我又花了半天时间写。想到了个自认为很厉害的函数,就是直接递归每种锁定的情况,然后去求对应的得分期望,找到最高的得分期望对应的锁定方案就好了,这样函数就非常简洁明了不用写一堆if去判断,只用了比较短的代码量就实现了。编写函数的过程有点曲折例如函数内部定义一个递归函数然后调用,这种写法我之前没写过但是要实现我的算法只能这么写,就不断尝试终于函数能得出正确的结果了。人机对战也就做完了,有了本地对战的基础还是很快的。
在线对战就是最逆天的部分了。我和队友光是实现在对战界面的头像的绑定就花了足足3小时,老是有的人有头像有的没头像。又因为一开始写代码埋了个大坑,房间退出后但是玩家信息依旧保存在文件内,导致房间不可再次进入,一开始我们都没发现这个问题,只好每次测试都重新扫一次编译把数据清除再生成的二维码,测试过程及其繁琐,感觉一个晚上扫了100次二维码了。最后终于把在线对战做完了。在最后一天才发现怎么改,改完这个bug后在线对战也彻底做完了。
作业做的过程自然是十分的折磨。但是每次做完一个功能,看到自己写的一行行代码变成可以具体实现在手机上的功能时心里还是十分有成就感。这次的作业还是让我收获到了很多,不仅提升了编程能力,还提高了团队协作的能力。毕竟是自己写的第一个微信小程序,写博客期间已经提交审核准备发布了,希望能在微信上真正玩到自己写的小程序,日后有更高的水平后再进行更多的完善。

林文光:
1.首先感谢队友,之前个人编程的时候为了解决性能分析、代码质量检测、单元测试、编写博客框架等算法外操作抓破了头,这次由这个打手包办了。之前想着国庆狠狠压力他写代码,结果自己国庆跟同学去团立项玩了4天,心里还蛮愧疚的。总之我的第一心得就是选对正确的队友绝对是成功的第一步。
2.我不会说如果再给我几天时间我能交出更好的结果,但这就是事实。所有开放人员都是戴着DDL这个镣铐跳舞的。要是有人身价过亿,只把代码当作兴趣,不受DDL困扰,兴致来了就写两句,兴致散了就去做别的,那我还挺羡慕的。在有限的时间里交出尽量好的答卷,需要的是强大的效率和时间管理能力,我还远远不够。
3.结对过程中最难忘的时刻就是一起写在线对战的时候,因为对战算法是他实现的,服务器通讯的知识是我学的,两个人在一台电脑、两个屏幕前修改了三个夜晚。期间不断的出bug与debug,虽然算不上为了写项目熬夜,但我觉得这也是程序猿的浪漫之一。
4.这次作业也是要求一大堆我没学过的知识,总体难度是很高的,但在这近乎一个月的时间内我真不好说什么难,感觉现在大部分的遗憾都是源于自己不够卷。
5.这次彩蛋是我设计的,要说有班级特色我马上就想到了梁sir和bossKe(BoosKe在个人作业总结PPT里给了我这么多戏份是在是受宠若惊),可以说是压迫感拉满了。为了平衡压迫感我把我们ai呦小队也加了进去,希望能博君一笑。
6.笔扬得万意,笔落韵未尽。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在风能领域,准确预测风速对于风电场的运行与管理至关重要。Matlab作为一个强大的数学计算和数据分析平台,被广泛应用于风速预测模型的构建。本文将深入探讨基于四种风速——随机风、基本风、阵风和渐变风的组合风速预测技术。 我们来理解这四种风速类型: 1. **随机风**:随机风是指风速呈现出随机性的变化,通常由大气湍流引起。在建模中,通常通过统计方法如高斯分布或Weibull分布来模拟这种不确定性。 2. **基本风**:基本风速是指在无特定扰动条件下的平均风速,它是长期观测结果的平均值,通常用于结构设计和风能评估。 3. **阵风**:阵风是短时间内风速显著增强的现象,对建筑物和风力发电机造成的主要威胁之一。阵风的预测涉及到风的脉动特性分析。 4. **渐变风**:渐变风是指风速随时间和空间逐渐变化的过程,常见于风向转变或地形影响下的风场变化。 在Matlab中,利用这四种风速类型进行组合预测,可以提高预测的准确性。预测模型可能包括以下几个步骤: 1. **数据收集与预处理**:收集历史风速数据,包括随机风、基本风、阵风和渐变风的数据,进行异常值检测、缺失值填充以及数据标准化。 2. **特征程**:提取风速变化的相关特征,如平均值、标准差、极值、频率分布等,这些特征可能对预测有重要影响。 3. **模型选择**:可以选择多种预测模型,如时间序列分析(ARIMA、状态空间模型等)、机器学习算法(线性回归、决策树、支持向量机、神经网络等)或深度学习模型(LSTM、GRU等)。 4. **模型训练**:利用历史数据训练选定的模型,调整模型参数以优化性能,例如通过交叉验证来避免过拟合。 5. **模型验证与评估**:使用独立的测试集验证模型预测效果,常见的评估指标有均方误差(MSE)、平均绝对误差(MAE)和决定系数(R²)。 6. **组合预测**:结合四种风速的不同模型预测结果,可以采用加权平均、集成学习(如bagging、boosting)等方式,以提升整体预测精度。 7. **实时更新与动态调整**:实际应用中,模型需要不断接收新的风速数据并进行在线更新,以适应风场环境的变化。 通过以上步骤,可以构建一个综合考虑各种风速特性的预测系统,这对于风电场的功率输出预测、风电设备的维护计划以及电网调度都具有重要价值。然而,需要注意的是,每个风场的地理环境、气候条件和设备状况都有所不同,因此模型的建立应根据实际情况进行定制和优
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值