2022软工K班结对编程任务

github:https://github.com/1daimon/main.git
bilibili:https://www.bilibili.com/video/BV1m8411W7wS/
小程序名称:北冥有鱼plus 在这里插入图片描述

一、结对探索(4分)

1.1 队伍基本信息(1分)

结对编号:13 ;队伍名称:煤油忒涩

学号姓名作业博客链接具体分工
032002109陈艺栋文本程序主体,前端,后端,ai算法
032002107陈曦文本ui原型,ai算法、介绍视频

1.2 描述结对的过程(1分)

很明显,看学号,同一宿舍,好沟通,意见不同了,可以直接线下solo,反正人跑不掉。

1.3 非摆拍的两人在讨论设计或结对编程过程的照片(2分)在这里插入图片描述

二、原型设计(16分)

2.1 原型工具的选择(2分)

我们选择了Pixso作为原型工具,这款工具的优势在于操作界面简单、功能丰富;内建资源多且免费;实时自动云存储且支持多人线上协作。打开文件速度快,操作丝滑,且入门难度不大,因而我们选择了它。

2.2 遇到的困难与解决办法(3分)

困难1:现成的素材库的素材如果直接用在原型中,会显得很违和,比如:一些按钮看起来不像按钮,反倒像花纹;
困难2:在进行背景图、主题图和logo设计时,能够找到的素材总是达不到我们的预想;
对困难1和困难2的解决方法:利用iPad的绘图软件Procreate对按钮图标、背景图、Logo、棋盘图等进行加工绘制,结合PS软件对相关图片进行处理调整,再运用至原型中,视觉效果有所改良。
困难3:设计Logo时,鲲鲲很难画好看,设计棋盘时,棋盘中间分界的小篆书很难写好看;
对困难3的解决方法:一点一点慢慢改,总会好看的。

​2.3 原型作品链接(5分)

https://pixso.cn/app/share/f/Beznd0zwclsWpTjfUTnNXwKBHS-OdrWM

2.4 原型界面图片展示(6分)

整体预览
在这里插入图片描述
菜单页
在这里插入图片描述
注册页

登录页
在这里插入图片描述
个人主页
在这里插入图片描述
对战页
在这里插入图片描述

游戏规则页
在这里插入图片描述

我们的创新点

1.独具特色的LOGO
在这里插入图片描述

2.(重要!)后端采用unicloud 开发

三、编程实现(14分)

3.1 网络接口的使用(2分)

通过unicloud云开发进行后端接口的开发实现登录注册登出等等基础接口,使用java语言完成websocket的开发实现在线联机对战。

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

前后端
在这里插入图片描述
在这里插入图片描述
AI部分
在这里插入图片描述

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

前端部分逻辑

比较复杂的点关键在于,如何在对战对己方与对方的操作进行限制,防止错误操作导致游戏的混乱,即判断本方是否投掷骰子,本方是否下子,对方是否投掷骰子,对方是否下子。通过对一些变量的判断更改来进行提示与限制。
在这里插入图片描述

websocket

websocket实现的关键就是什么时候接收值什么时候传送值,如何实现客户端与服务器的互通。在进行在线对战时,先进入房间等待,服务器储存在房间等待的用户信息,随机分配对手,进入对战后,监听每个玩家的操作,并将落子结果与棋盘结果传送到服务器并由服务器将之转发给另一方。
在这里插入图片描述
在这里插入图片描述

AI部分逻辑

简单模式算法流程图如下:关键在于搜索未填格子最多的行
在这里插入图片描述
困难模式算法流程图如下:关键在于通过模拟计算得到下在每一格的预估收益,并且根据已填格子情况进行修改。
在这里插入图片描述

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

前端

step_in1(index){
				if(this.please==0){
					this.dialogToggle1('info')
					return
				}
				if(this.click==1){
					this.dialogToggle3('info')
					return
				}
				if(this.use2==1){
					this.dialogToggle1('info')
					return
				}
				if(this.sum1[index]!=0){
					this.dialogToggle4('info')
					return
				}
				this.changenum1=1
				this.ready1=1
				this.click=1
				this.ready2=true
				console.log(index)
				this.sum1[index]=this.result_num
				this.ima_list1[index]=this.array_t[this.result_num-1]
				console.log((this.ima_list1[index]))
				this.ready=true
				this.isShow= false// 更新dom
				this.$nextTick(()=>{
				this.isShow = true
				    })
				this.use1=0
				this.click=1
				this.full=this.check(this.sum1)
			
				if(0<=index &&index<3){
					for (var i=0;i<3;i++){
						if(this.sum2[i]==this.sum1[index]){
							this.sum2[i]=0
							this.ima_list2[i]=''
						}
					}
						this.isShow= false// 更新dom
						this.$nextTick(()=>{
						this.isShow = true
						    })
				}else if(3<=index &&index<6){
					for (var i=3;i<6;i++){
						if(this.sum2[i]==this.sum1[index]){
							this.sum2[i]=0
							this.ima_list2[i]=''
						}
					}
					this.isShow= false// 更新dom
					this.$nextTick(()=>{
					this.isShow = true
					    })
				}else{
					for (var i=6;i<9;i++){
						if(this.sum2[i]==this.sum1[index]){
							this.sum2[i]=0
							this.ima_list2[i]=''
						}
					}
					this.isShow= false// 更新dom
					this.$nextTick(()=>{
					this.isShow = true
					    })
				}
				if(this.full==1){
					console.log(this.sum1,this.sum2)
					this.score1=this.calculate(this.sum1)
					this.score2=this.calculate(this.sum2)
					if(this.score1>this.score2){
						this.winner='user1'
					}else{
						this.winner='user2'
					}
				
					this.message = `用户分数user1:${this.score1}user2:${this.score2}胜者:${this.winner}`
					this.dialogToggle5('info')
					return 
				}
			},
			step_in2(index){
				if(this.please==0){
					this.dialogToggle1('info')
					return
				}
				if(this.click==1){
					this.dialogToggle3('info')
					return
				}
				if(this.use1==1){
					this.dialogToggle1('info')
					return
				} 
				if(this.sum2[index]!=0){
					this.dialogToggle4('info')
					return
				}
				this.changenum2=1
				this.ready2=1
				this.click=1
				console.log(index)
				this.ready1=true
				this.sum2[index]=this.result_num
				this.ima_list2[index]=this.array_t[this.result_num-1]
				console.log((this.ima_list2[index]))
				this.isShow= false// 更新dom
				this.$nextTick(()=>{
				this.isShow = true
				    })
				this.use2=0
				this.click=1
				this.full=this.check(this.sum2)
				
				if(0<=index &&index<3){
					for (var i=0;i<3;i++){
						if(this.sum1[i]==this.sum2[index]){
							this.sum1[i]=0
							this.ima_list1[i]=''
						}
					}
						this.isShow= false// 更新dom
						this.$nextTick(()=>{
						this.isShow = true
						    })
				}else if(3<=index &&index<6){
					for (var i=3;i<6;i++){
						if(this.sum1[i]==this.sum2[index]){
							this.sum1[i]=0
							this.ima_list1[i]=''
						}
					}
					this.isShow= false// 更新dom
					this.$nextTick(()=>{
					this.isShow = true
					    })
				}else{
					for (var i=6;i<9;i++){
						if(this.sum1[i]==this.sum2[index]){
							this.sum1[i]=0
							this.ima_list1[i]=''
						}
					}
					this.isShow= false// 更新dom
					this.$nextTick(()=>{
					this.isShow = true
					    })
				}
			if(this.full==1){
				this.score1=this.calculate(this.sum1)
				this.score2=this.calculate(this.sum2)
				if(this.score1>this.score2){
					this.winner='user1'
				}else{
					this.winner='user2'
				}
				this.message = `用户分数:  user1:${this.score1} ; user2:${this.score2} ; 胜者 ${this.winner}`
				this.dialogToggle5('info')
				return
			}
			},

这一部分是前端本地对战时,棋盘的点击事件函数,通过一些变量来控制操作的正确性,如判断是否已经摇过骰子,是否已经点击下子,是否下在本方棋盘,是否下载空格子,以及一些游戏规则的实现,如判断同行是否消去,游戏是否结束等。

后端

  @OnOpen
  public void onOpen( Session session){
      this.session = session;
      index++;
      try {
          Result result = new Result();
          if(index%2==0){
              WebSocket socket1 = webSocketMap.get((index-1)+"");
              if(socket1!=null){
                  result.setBout(true);
                  result.setMessage("系统:游戏开始,请您先掷骰!");
                  result.setColor("black");
                  JSONObject json1 = JSONObject.fromObject(result);
                  socket1.sendMessage(json1.toString());
                  //对先掷骰的对象发送数据结束
                  result.setMessage("系统:游戏开始,请等待对手掷骰!");
                  result.setBout(false);
                  result.setColor("white");
                  this.sendMessage(JSONObject.fromObject(result).toString());
                  //对后出手的发送消息结束
              }else{
                  index--;
                  result.setMessage("系统:等待玩家匹配!");
                  this.sendMessage(JSONObject.fromObject(result).toString());
              }
          }else{
              result.setMessage("系统:等待玩家匹配!");
              this.sendMessage(JSONObject.fromObject(result).toString());
          }
          this.mykey = index;
          webSocketMap.put(mykey+"", this);     //加入map中
          System.out.println(webSocketMap.size());
    } catch (Exception e) {
        e.printStackTrace();
    }
  }
  @OnMessage
  public void onMessage(String message) {
      System.out.println(message);
      JSONObject json = JSONObject.fromObject(message);
      Result result = (Result) JSONObject.toBean(json,Result.class);
      try {
          WebSocket socket = null;
          if(mykey%2==0){
              socket = webSocketMap.get((mykey-1)+"");
          }else{
              socket = webSocketMap.get((mykey+1)+"");
          }
          if(socket!=null){
              if(result.getXy()!=null&&!"".equals(result.getXy())){//有坐标表示为落子,反之则为发送信息
                  this.sendMessage(message);
                  result.setBout(true);//对手的bout改为true,表示接下来可以掷骰
                  result.setMessage("系统:对方已掷骰,正在等待您掷骰!");
                  socket.sendMessage(JSONObject.fromObject(result).toString());
              }else{
                  Result newResult = new Result();
                  newResult.setMessage("自己:"+result.getMessage());
                  this.sendMessage(JSONObject.fromObject(newResult).toString());
                  newResult.setMessage("对方:"+result.getMessage());
                  socket.sendMessage(JSONObject.fromObject(newResult).toString());
              }
          }
          
          
    } catch (Exception e) {
        e.printStackTrace();
    }
  }

这一部分代码就是websocket其中的一段逻辑,其中就是判断房间内是否有人,对手是否匹配完成,对战是否开始。以及对于对方落子与棋盘的信息的转发。使用websocket主要是为了减少网络资源的损耗,而不必像http那样用轮询的方式来进行信息的接收与发送。

AI

int nextStep(int ownBoard[],int otherBoard[],int figure){
    float Block[3]={3,3,3};
    for(int i=0;i<9;i++){//获取格子填充情况
        if(ownBoard[i]!=0){
            if(i>=0&&i<=2) Block[0]--;
            else if(i>=3&&i<=5) Block[1]--;
            else if(i>=6&&i<=8) Block[2]--;
        }
    }//
    int ownScore=Getscore(ownBoard);
    int otherScore=Getscore(otherBoard);
    //for(int i=0;i<9;i++) cout<<ownBoard[i]<<" ";
    float D_value[9]={0};//计算操作差值
    for(int i=0;i<9;i++){
        if(ownBoard[i]==0) {
            int own1[9];
            for(int ii=0;ii<9;ii++) {
                own1[ii]=ownBoard[ii];
                //cout<<own1[ii]<<" ";
            }
            //cout<<endl;
            //复制一个ownboard
            int other1[9];
            for(int ii=0;ii<9;ii++) {
                other1[ii]=otherBoard[ii];//复制一个otherboard
                //cout<<other1[ii]<<" ";
            }
            //cout<<endl;

            //对数组进行操作
            own1[i]=figure;
            if(i>=0&&i<=2){//同行消灭处理
                for(int j=0;j<=2;j++){
                    if(other1[j]==figure) other1[j]=0;
                }
            }
            else if(i>=3&&i<=5){
                for(int j=3;j<=5;j++){
                    if(other1[j]==figure) other1[j]=0;
                }
            }
            else if(i>=6&&i<=8){
                for(int j=6;j<=8;j++){
                    if(other1[j]==figure) other1[j]=0;
                }
            }

            int D_ownscore=Getscore(own1)-Getscore(ownBoard);
            int D_otherscore=Getscore(otherBoard)-Getscore(other1);
            int Differ=D_otherscore+D_ownscore;//获取操作后的差值
            D_value[i]=Differ;

        }
    }//至此D_value的值已经初步形成
    for(int i=0;i<3;i++){//根据已知格子权重赋值
        if(Block[i]!=0)
        switch (i)
        {
        case 0:
            for(int j=0;j<3;j++){
                if(D_value[j]!=0) {D_value[j]+=Block[0]*2.5;
                if(Block[0]==3) D_value[j];
                if(Block[0]==2) D_value[j]-=0;
                if(Block[0]==1) D_value[j]-=3;
                if(D_value[j]<0) D_value[j]=0.1;
                }
            }
            break;
        case 1:
            for(int j=3;j<6;j++){
                if(D_value[j]!=0) {D_value[j]+=Block[1]*2.5;
                if(Block[1]==3) D_value[j];
                if(Block[1]==2) D_value[j]-=0;
                if(Block[1]==1) D_value[j]-=3;
                if(D_value[j]<0) D_value[j]=0.1;
                }
            }
            break;
        case 2:
            for(int j=6;j<9;j++){
                if(D_value[j]!=0) {D_value[j]+=Block[2]*2.5;
                if(Block[2]==3) D_value[j];
                if(Block[2]==2) D_value[j]-=0;
                if(Block[2]==1) D_value[j]-=3;
                if(D_value[j]<0) D_value[j]=0.1;
                }
            }
            break;
        default:
            break;
        }
    }
    float max = 0;
    float Di_value[9]={0.0};
    int flag=0;
    for(int i=0;i<9;i++) {
        //cout<<D_value[i]<<" ";
        if(D_value[i]>max){
            max=D_value[i];
            flag=i;
        }
    }
    //cout<<endl;
    return flag;
}

本函数中,先用Block数组记录已填格子情况,然后复制用于操作的数组,一个一个计算预计收益存入D_value数组中,再根据已填格子情况对预计收益进行调整,最后寻找预计收益最大的位置输出。

​3.5 性能分析与改进(2分)

在这里插入图片描述

分析:存在一些代码冗杂,一些页面渲染的体验效果不是特别好,个别函数运行效率较为不理想。
改进:将复用多次的代码进行封装,将函数逻辑精简并减少循环。

3.6 单元测试(2分)

前端
前端采用console.log进行测试
在这里插入图片描述
在这里插入图片描述
后端
后端采用postman进行接口测试
在这里插入图片描述

3.7 贴出GitHub的代码签入记录,合理记录commit信息(2分)

在这里插入图片描述

四、总结反思(11分)

4.1 本次任务的PSP表格(2分)

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

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

陈艺栋

第N周新增代码(行)累计代码(行)本周学习耗时(小时)累计学习耗时(小时)重要成长
1003030学习了一下AI理论,以及一点后端知识
2321532153161学懂了websocket,可惜的是学废了AI,理论与实践有差距, 但是完成了大部分的前端逻辑以及渲染
3462278373091学了一下unicloud,体验了一下云开发,用云开发解决了除了websocket外的所有后端接口,websocket用java的框架实现

陈曦

第N周新增代码(行)累计代码(行)本周学习耗时(小时)累计学习耗时(小时)重要成长
10088学会了原型软件pixso的用法,开始用Procreate、pixso、PS做UI设计
200917学习websocket,但没有投入实际使用,完成了原型设计
3482482825完成了三种难度模式的AI,完成了测试程序

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

我们最初想象中的产品是界面简洁美观,带有一点中国古风的、满足账号登录登出、能够实现本地对战、人机对战、在线对战三大功能,其中人机对战适配有不同的难度的、带有在线排行榜单、个人成绩记录等功能的、人机交互友好,用户操作舒适的微信小程序。
原型设计作品做得差强人意,UI设计方面,由于原型设计者有些艺术细胞,但软件技术有待提高,小程序界面较为美观,但不够简约,在图标和按钮设计和原型交互设计上,原型设计者也遇到了各种问题,不得不将要求一再放低,在原型上没能实现模拟对战,是本次原型设计的一大遗憾(原型设计者真的很努力在做了QAQ)
最终开发出的软件是较为接近最初设定的产品形态的,除在线排行榜单功能,最初计划的功能均已实现。主要差距在用户体验不够尽善尽美,实际对战过程中,存在偶发的卡顿现象,是本软件的瑕疵。

4.4 评价你的队友

陈艺栋:我的队友陈曦值得学习的地方在于他的灵活运用能力比较强,能够想到各种方法、活用会用的工具应对遇到的问题,在美工设计上能够设计出符合一般审美的图形、图像,安排的任务也会尽心尽力去做;需要改进的地方在于他的工作和学习效率不够高,而且时间管理能力有待提高,经常出现时间安排不过来的情况,常导致作息不规律,精力不集中,这样效率就更不高了。

陈曦:我的队友陈艺栋给人一种很踏实的感觉,他学习能力强、编程能力强,在本次作业中完成了最为复杂、工作量和难度都非常大的前后端,让我有一种抱大腿的感觉;他值得学习的地方是优秀的时间管理能力和强大的自律性,这两点都是我欠缺的。需要改进的地方是为我安排任务时表达得不是很详细具体,稍稍有些缺少与我的沟通。

​ 4.5 结对编程作业心得体会(3分)

-陈艺栋

作业难度:单说作业难度的话,最为困难的话对我而言是实现websocket和AI,因为之前没怎么碰过后端以及AI的训练.其它的总体而言难度还好.
完成后的感受:爽,很爽,非常爽,卸下了一个包袱,就是有些地方自己不是很满意.
代码模块异常:恶心我最久的就是websocket的实现,因为没碰过,要从0到1有点难度,还有就是unicloud的实现与流程也常报错.
结对困难与解决方法:困难就在于AI与后端,我俩都不太擅长.所以我俩就学,直接莽上去,撸起袖子加油熬夜.
启发:与队员的合作要经常交流,同时时间安排一定要合理,避免项目的拖延以及实现效果的不足.还有就是要有去碰壁去解决难题的勇气.困难在开发过程中一定是一个主旋律.

-陈曦

作业难度:对我来说这个作业是比较难的,因为我没有什么开发经验,很多东西都不懂,幸好有一个很好的队友.
完成后的感受:终于结束了,感觉自己又活过来了
代码模块异常:在我们训练学术ai失败后,选择用算法来模拟ai,这一块我的出错比较多,逻辑不够清晰.
结对困难与解决困难:困难就在于AI与后端,撸起袖子加油熬夜,可惜最后ai的训练训出了一个鬼东西,不如自己重写一个算法.
启发:学习要趁早,不要书到用时方恨少,就是自己的能力不足,导致此次项目完成的比较痛苦.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值