2022软工K班结对编程任务

仓库(github)

视频(bilibili)

一、结对探索

1.1 队伍基本信息

结对编号:9;队伍名称:这个人很懒

学号姓名作业博客链接具体分工
012004114林毅后端,原型设计,视频剪辑
052004130潘思源前端,UI,AI

1.2 描述结对的过程

[(img-bqUtqN1E-1665715490649)(C:%5CUsers%5CLenovo%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20221014103519766.png)]

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

(bushi)
在这里插入图片描述

二、原型设计

2.1 原型工具的选择

墨刀 简单易上手 (bushi 看大家都用这个

2.2 遇到的困难与解决办法

困难:对于一下比较精美的图片墨刀需要收费,不愿意当大冤种充钱,就要想些解决方法

解决办法:对于一些比较精美的logo就选择用自己用ipad的procreate进行绘制

2.3 原型作品链接

https://modao.cc/app/eu97UaLbrjp720huGEpDvl #逍遥骰原型-分享

2.4 原型界面图片展示

创新点1:提供在线和离线两个模式,在线模式用户可以根据自己的id来在线匹配对手,离线模式用户只可享受人机对战和本地双人对战的功能

创新点2:人机对战,笔者设计了6个难度的AI算法来适配不同的用户,只有通过前面的关卡才能解锁后面的关卡。

在线功能提供:人机对战、本地双人对战、在线匹配对手对战

离线功能提供:人机对战、本地双人对战

本地双人对战:两玩家在同一台设备上进行操作,盘面可供两人操作

在线匹配对手对战:玩家通过将自己的id发送请求,并等待响应,在超时时间范围内,若有人同样发起请求,则服务器向这两玩家做出响应,两者可在线对战。

两创新点的原型图片如下:

其他功能UI如下:

三、编程实现

3.1 网络接口的使用

一些将为初级的接口就为普通的请求响应

验证码功能需将邮箱权限放开,与服务器进行交互后返回可用的接口

在线匹配对战时,利用websocket实现即时通讯,后端监听连接的建立和数据的转发,前端利用socket的onopen、onmessage等接口进行信息交互

后端利用postman进行接口测试,具体测试接口如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mFMqOisL-1665715490652)(C:%5CUsers%5CLenovo%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20221014104352959.png)]

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

前端:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NarbCO0k-1665715490652)(.\2.PNG)]

后端:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDGgwt56-1665715490653)(.\15.png)]

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

其实ai每一步最多只有三种不同的选择,我通过一个双层神经网络(第一层的输入是一个19维向量)给每一种选择评分

取最高分的选择为输出结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oJvKMJD9-1665715490654)(.\17.jpg)]

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

前端:

  void updatemat(int sign, List<int> newmat, index, context) {//这是PvP版,PvE调用aimove函数,联机对战监听websocket
    if (sign == 0) {
      updata = newmat;
    } else {
      downdata = newmat;
    }
    check(sign, index);//检查是否有消除

    downpoint = updatePoint(downdata);//更新矩阵
    uppoint = updatePoint(updata);
    turn == 0 ? turn = 1 : turn = 0;//切换回合
    check_terminal(context);//检查游戏是否结束
    print(turn);
    notifyListeners();//重新渲染画面
  }

//函数在widget tree 顶端工作,负责重新渲染/同步数据
  void connect(String id, context) {
    channel = IOWebSocketChannel.connect('ws://120.77.79.99:8085/imserver/$id');
    channel.stream.listen((event) {//监听websocket数据
      print(event);
      if (r == 0) {
        Map<String, dynamic> data = jsonDecode(event.toString());//json解码

        updata = parse(data["mat2"]);//更新数据
        downdata = parse(data["mat1"]);
        downpoint = updatePoint(updata);//更新分数
        uppoint = updatePoint(downdata);
        print("\n\n\n\n\ndata received");
        turn = 1;
        check_terminal(context);//检查游戏结束
        notifyListeners();
      }
      if (r == 1) r = 0;
    });
  }

后端:

@ServerEndpoint("/imserver/{userId}")
@Component
public class WebSocketServer {

    static Log log=LogFactory.get(WebSocketServer.class);
    /**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
    private static int onlineCount = 0;
    /**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
    private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
    private Session session;
    /**接收userId*/
    private String userId="";

    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session,@PathParam("userId") String userId) {
        this.session = session;
        this.userId=userId;
        if(webSocketMap.containsKey(userId)){
            webSocketMap.remove(userId);
            webSocketMap.put(userId,this);
            //加入set中
        }else{
            webSocketMap.put(userId,this);
            //加入set中
            addOnlineCount();
            //在线数加1
        }

        log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());

        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            log.error("用户:"+userId+",网络异常!!!!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if(webSocketMap.containsKey(userId)){
            webSocketMap.remove(userId);
            //从set中删除
            subOnlineCount();
        }
        log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("用户消息:"+userId+",报文:"+message);
        //可以群发消息
        //消息保存到数据库、redis
        if(StringUtils.isNotBlank(message)){
            try {
                //解析发送的报文
                JSONObject jsonObject = JSON.parseObject(message);
                //追加发送人(防止串改)
                jsonObject.put("fromUserId",this.userId);
                String toUserId=jsonObject.getString("toUserId");
                //传送给对应toUserId用户的websocket
                if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){
                    webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
                }else{
                    log.error("请求的userId:"+toUserId+"不在该服务器上");
                    //否则不在这个服务器上,发送到mysql或者redis
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
        error.printStackTrace();
    }
    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    /**
     * 发送自定义消息
     * */
    public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
        log.info("发送消息到:"+userId+",报文:"+message);
        if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
            webSocketMap.get(userId).sendMessage(message);
        }else{
            log.error("用户"+userId+",不在线!");
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}

3.5 性能分析与改进

前端:减少重新渲染的范围,优化widget tree 结构,每次数据更新时仅局部刷新,只在数据会被用户看到或空闲的时候重新渲染

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDKjDuVi-1665715490654)(.\18.png)]

后端:可以看出,当对每一个函数进行调用时,CPU的占用率并不大,外部调用时所占用CPU较大(关闭项目操作)。故此时的接口的性能都较优。

但小游戏应考虑若有很多玩家同时并发发起匹配请求的高并发问题(或许像羊了个羊一样火了呢(bushi)),故笔者利用redis缓存缓存玩家请求数据,并将其发送至Kafka消息队列,进行逐一处理以解决并发、大数据问题。

3.6 单元测试

@SpringBootTest
class XiaoYaoShaiApplicationTests {

   @Test
   void contextLoads() {
   }

   @Autowired
   private UserMapper userMapper;

   /**
    * mybatis-plus测试
    */
   @Test
   void testInsert(){
       int result = userMapper.insert(new User("228751565@qq.com","123456"));
       System.out.println("result:" + result);
   }
   @Autowired(required = false)
   private RedisUtil redisUtil;

   /**
    * redis缓冲测试
    */
   @Test
   void getName(){
       redisUtil.set("2287511565@qq.com","5654");
       redisUtil.expire("2287511565@qq.com",30);
       System.out.println(redisUtil.get("2287511565@qq.com"));
       System.out.println(redisUtil.getExpire("2287511565@qq.com"));
           String sources = "0123456789"; // 加上一些字母,就可以生成pc站的验证码了
           Random rand = new Random();
           StringBuffer flag = new StringBuffer();
           for (int j = 0; j < 6; j++)
           {
               flag.append(sources.charAt(rand.nextInt(9)) + "");
           }
           System.out.println(flag.toString());
   }
}

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

四、总结反思

4.1 本次任务的PSP表格

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

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

前端:

N周新增代码(行)累计代码(行)本周学习耗时(小时)累计学习耗时(小时)重要成长
1202000深刻反思了自己的懒惰
220022011深刻反思了自己的懒惰
3398042003940学到了生命的可贵;人类在茫茫的宇宙中是多么的渺小;人类的赞歌就是勇气的赞歌,人类在极度危险的环境下能激发出无限的可能!

后端:

N周新增代码(行)累计代码(行)本周学习耗时(小时)累计学习耗时(小时)重要成长
10000嵌入式学明白了一点
20000接口学明白了一点
3217321733030对很久没用的技术重新巩固了一遍,加深了自己对其的理解。对服务器上的数据库加密更熟练了一点(被攻击太多次了),websocket连接有了更深的理解

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

有很多情况在一开始设计的时候没有设想的,开发过程中花了很多时间fix,然后实际运行时又出现了很多开发过程中没有预想到的情况,又花了很多时间。主要是游戏逻辑,和联机同步问题比较麻烦。

一开始想法比较简单,感觉小游戏用不了多久就能搞定,于是乎前两周就没去新建文件夹。等开始筹备的时候,总是把各个操作想的太简单,中途也遇到了很多bug,很多一开始预想的功能页面再最后只能不断删减。

产品基本实现了最初设想。

4.4 评价你的队友

潘思源

队友:林毅

值得学习的地方:

  • 代码易懂、简单、高效
  • 逻辑清晰
  • 代码高内聚,低耦合

需要改进的地方:

  • 射手打得稀烂

林毅

队友:潘思源

值得学习的地方:

  • 代码能力强,技术栈丰富
  • 勤奋刻苦,为了赶项目4点睡8点起
  • 思维开阔,能有很多idea

需要改进的地方:

  • 太努力了,制造焦虑

4.5 结对编程作业心得体会

潘思源:

一定要早点开工啊,不要拖啊。

前期准备比实际写代码更重要,前后端对接一定要事先沟通,一边写一边商量很痛苦

ai一开始打算用强化学习的,后来一直拖就没去学。所以说啊,不能拖!

写代码该重构就重构,不要把时间浪费在屎山上。

测试过了不意味着实机能跑,问题只会在运行过程中出现。

林毅:

因为时间问题导致最后没能交出一份满意的项目实属可惜。

bin的名言:“一个项目的工程量要按自己设想的两倍来计算。”这次算是狠狠的领教到了

本次作业的难度在基础功能上不算难,匹配机制、即时通讯都不算难,但可发挥性很大。AI算法可以算是能够无限卷下去的方向,研究的过程会触及到之前很多领域没有涉猎过的知识,很八错,很有趣。

在后端的开发上也比较能够得心应手了,单人进行后端开发目前认为问题不大,等到后期项目比较大时,涉及到多模块多人合作的后端开发,到时可能会遇上一些较为棘手的问题。

这次可惜没有往小程序上线方向走,下次有机会想尝试一下上线一个小程序。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值