【五子棋】

news2024/11/15 8:32:04

五子棋


文章目录

  • 五子棋
  • 前言
  • 一、登录功能
  • 二.哈希表管理用户的会话和房间
  • 三.基于Websocket连接开发的功能
    • 1.匹配功能
    • 2.游戏房间
    • 3.挑战功能
    • 4.人机对战
    • 5.聊天功能


前言

这篇博客主要详细介绍我的五子棋项目的核心功能的实现细节,也就是详细介绍五子棋各个功能是如何实现前后端交互的。
由于该项目对数据要求做实时性的发送和接收,所以大部分的API都是基于websocket来实现的。
并且我会按照我在开发该项目时的思路来写这篇博客,比如:在开发一个匹配功能时,我会先约定好前后端的交互发送请求的接收响应的接口详情,然后就是编写前端和后端代码。


一、登录功能

功能详情:不支持游客,必须登录才能进行游戏。
思路分析:数据库有张用户表,记录着用户的信息,登录功能就是通过前端发送数据,然后在后端根根据用户名查表并且进行密码校验,考虑到该项目只是一个小游戏,所以没有对密码采用MD5+盐算法.

1.约定前后端交互接口
前端发送的http请求我设计这个样子:
在这里插入图片描述
后端处理完请求之后返回的响应,我设计成这个样子:
在这里插入图片描述
2.编写前端代码
这里解释一下,这里只是贴出了前端代码的核心内容,就是通过一个点击事件触发mysub()这个函数,然后给后端发送ajax请求.

    function mysub(){
        // 1.非空效验
        var username = jQuery("#username");
        var password = jQuery("#password");
        if(username.val()==""){
            alert("请先输入用户名!");
            username.focus();
            return;
        }
        if(password.val()==""){
            alert("请先输入密码!");
            password.focus();
            return;
        }
        // 2.ajax 请求登录接口
        jQuery.ajax({
            url:"/user/login",
            type:"POST",
            data:{"username":username.val(),"password":password.val()},
            success:function(result){
                if(result!=null && result.code==200 && result.data!=null){
                    // 登录成功
                    location.href = "game_hall.html";
                    // location.replace("/game_hall.html")
                }else{
                    alert("抱歉登录失败,用户名或密码输入错误,请重试!");
                }
            }
        });
    }

3.后端代码
由于这是一个ssm项目,所以我先解释一下项目的目录结构:
在这里插入图片描述

这里我就贴出我controller层的一段代码:

    @RequestMapping("/login")
    public User login(String username, String password){
        //参数非空校验
        if (username == null || password == null){
            AjaxResult.failed(-1,"登录失败",null);
        }
        //获取user表里username对应的密码
        User user =userService.getUserByusername(username);
//        if (user == null || !password.equals(user.getPassword())){
//            return AjaxResult.failed(-1,"登录失败",null);
//        }
        //创建session会话
        HttpSession session = request.getSession(true);
        session.setAttribute(AppVariable.USER_SESSION_KEY,user);
        //屏蔽敏感信息
        user.setPassword(null);
//        return AjaxResult.success("登录成功",user);
        return user;
    }

更多的细节,可以通过码云访问我的码云项目源代码
然后注册功能和游戏大厅页面获取登录用户信息功能区别都不大,就是普通的进行数据库的插入和查询操作,这里不再赘述了.
提一点,我这里使用AOP面向切面的思想对登录功能做了拦截器和统一数据返回格式:
在这里插入图片描述
拦截器类:

public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 返回true,说明以登录
     * 返回false:未登录 跳转到登录页面
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute(AppVariable.USER_SESSION_KEY)!=null){
            //打印会话对象里的用户名
            User user = (User) session.getAttribute(AppVariable.USER_SESSION_KEY);
            System.out.println("拦截器,当前用户会话:"+user.getUsername());
            return true;
        }
        response.sendRedirect("/login.html");
        return false;
    }
}

注册里拦截器并设置拦截路由:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
                //静态页面
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/js/**")
                .excludePathPatterns("/register.html")
                .excludePathPatterns("/TestAPI.html")
                //接口
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/reg");
    }
}

统一数据返回格式:
在这里插入图片描述
自定义返回的数据格式:

public class AjaxResult {
    private Integer code;
    private String msg;
    private Object data;

    /**
     * 成功的完成一次请求
     * @param data
     * @return
     */
    public static AjaxResult success(Object data){
        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(200);
        ajaxResult.setMsg("请求成功");
        ajaxResult.setData(data);
        return ajaxResult;
    }
    public static AjaxResult success(String msg,Object data){
        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(200);
        ajaxResult.setMsg(msg);
        ajaxResult.setData(data);
        return ajaxResult;
    }

    /**
     * 没有成功的返回一次请求
     * @param code 根据不同的情况自定义 -1.-2
     * @param msg 根据不同的情况自定义:用户名为空....
     * @param data 根据不同的情况自定义:null
     * @return
     */
    public static AjaxResult failed(Integer code,String msg,Object data){
        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(code);
        ajaxResult.setMsg(msg);
        ajaxResult.setData(data);
        return ajaxResult;
    }
    public static AjaxResult failed(Integer code,String msg){
        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(code);
        ajaxResult.setMsg(msg);
        return ajaxResult;
    }
}

返回数据前对数据格式进行统一处理:

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    ObjectMapper objectMapper;//用于处理字符串
    /**
     * 开关,为true才会执行beforeBodyWrite方法
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }


    /**
     * 在response返回给前端之前会执行的方法,参数只关注body
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //如果body已经被封装成AjaxResult了,直接返回
        if(body instanceof AjaxResult){
            return body;
        }
        //如果body是string类型,得使用jackson序列化
        if(body instanceof String){
            return objectMapper.writeValueAsString(AjaxResult.success(body));
        }
        //如果没被封装,就调用success方法封装一下
        System.out.println("这里对响应进行了统一的封装处理");
        return AjaxResult.success(body);
    }
}


# 二、使用步骤
## 1.引入库
>代码如下(示例):

```c
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context

二.哈希表管理用户的会话和房间

使用了多张哈希表来管理userId和session对应的关系,roomId和room的对应关系,userId与room的对应关系,极大的简化的后序的开发。
在这里插入图片描述

三.基于Websocket连接开发的功能

1.匹配功能

功能详情: 在线用户通过"随机匹配",匹配到在线玩家进行五子棋对局.
思路分析:创建一系列的匹配队列,把根据分数梯段把触发"匹配功能"的用户放到同一个匹配队列里,然后把两个用户从队列里拿出来进行一句游戏。
队列具有"先进先出"的特性,这有就能简单实现“谁先来就开始游戏”的随机性。
当两名玩家比配成功之后,就安排两名玩家进入游戏。

1.约定前后端交互接口
在这里插入图片描述
2.前端代码:
创建websocket对象,点击事件触发send()函数给服务器发送‘’匹配请求‘’和"停止匹配请求",websocket.onmessage根据约定的前后端交互接口来处理逻辑。


```javascript
 //在此处进行初始化websocket,并且实现前端的匹配逻辑
  //此处的路径必须写作是 /findmatch,千万不要写作 /findmatch/
  let websocketurl = 'ws://'+location.host+'/findmatch'//location.host根据浏览器的html页面获取到服务器的ip和端口
  let websocket = new WebSocket(websocketurl)//ws://就是websocket协议名
  //回调函数
  websocket.onopen = function(){
    console.log('[MatchAPI]连接成功')

  }
  websocket.onerror = function () {
    console.log('[MatchAPI]出现异常')
    
  }
  //服务器
  websocket.onclose = function(){
    console.log('[MatchAPI]连接失败')

  }
  //监听页面关闭事件(前端页面关闭),在页面关闭(离开已经建立websocket连接的页面)之前,手动调用这里的websocket的close方法
  window.onbeforeunload = function(){
    websocket.close();
  }
  //重点实现,处理服务器返回的响应
  websocket.onmessage = function(e){

    //处理服务器返回的响应数据,这个响应就是针对:"开始匹配"/"停止匹配"来对应的
    //解析得到的响应对象,返回的数据是一个JSON字符串,先解析成 js 对象
    let resp = JSON.parse(e.data);

    if(!resp.ok){
      //ok不为true
        //有新用户上上线,调用
        if(resp){
            console.log("有新用户上线/下线,调用top10()");
            top10();
            return;
        }

        console.log("游戏大厅中接收到了失败响应" + resp.reason);
      return;
    }
    //响应的三种情况
    if(resp.message == 'startMatch'){
      //开始匹配请求发送成功
      console.log('进入匹配队列成功!');
      //修改button标签内容为  正在匹配中...(点击停止)
      matchButton.innerHTML='正在匹配中...(点击停止)';

    }else if(resp.message == 'stopMatch'){
      //停止匹配请求发送成功
      console.log('退出匹配队列成功!');
      //修改button标签内容为  开始匹配
      matchButton.innerHTML = '开始匹配';

    }else if(resp.message == 'matchSuccess'){
      //已经匹配到对手
      console.log('匹配到对手,进入游戏房间');
      location.assign('/game_room.html');
        // location.replace('/game_room.html')
    }else if(resp.message == 'repeatConnection'){
        //检查到多开
        console.log('检测到多开!当前用户已经在其他设备上登录!');
        location.assign('/login.html');
        //使用replace,浏览器不会将页面入栈保存
        // location.replace('/login.html')
    }
    else{
      //这三种情况之外的情况,认为是异常的情况
      console.log('收到了非法的响应! message='+resp.message);
    }


  }

  
  //点击按钮,给服务器发送匹配请求
  function send(){
    //在触发 websocket 请求之前,先确认一下 websocket 连接是否正常
    if(websocket.readyState == websocket.OPEN){
      //当readyState处在OPNE状态,说明连接是正常的
      //这里发送数据有两种可能: 1.开始匹配 2.停止匹配
      if (matchButton.innerHTML == '开始匹配'){
        //发送开始匹配的请求
        console.log('开始匹配');
        //发送json格式的请求
        websocket.send(JSON.stringify(
          {
            message: 'startMatch',
          }
          ));

      }else if(matchButton.innerHTML == '正在匹配中...(点击停止)'){
        //发送停止匹配的请求
        console.log('停止匹配')
        websocket.send(JSON.stringify(
          {
          message:'stopMatch',
          }
        ));
      }

    }else{
      //if没进入,到这里说明连接是不正常的
      alert('当前您的连接已断开!请重新登录!');
      location.assign('/login.html');
      alert("")
    }
  }

3.后端代码:
后端代码比较多,不是核心的我就不贴了,我在这里描述一下逻辑:
在这里插入图片描述
匹配的核心是Match类,这个类里创建了匹配队列,扫描匹配队列的线程和匹配函数:
在这里插入图片描述
首先是创建匹配队列:
在这里插入图片描述
然后创建多线程持续扫描队列:调用handleMatch()方法
在这里插入图片描述
handleMatch()方法
在这里插入图片描述
在这里插入图片描述
总结:点击匹配的用户会被加入到匹配队列里,不同分段匹配队列全局只需要一份,spring项目支持多线程所以存在线程安全问题,使用synchronized,匹配队列有线程在一直扫描该队列,为了保证cpu资源不被浪费是用来wait和notify,当队列里玩家少于2时,线程不用继续扫描了,等待该队列添新的用户时唤醒线程继续扫描,当队列元素为偶数时,可以匹配出两名玩家,并且为他们创建游戏房间并返回匹配成功的响应。
具体源代码:码云项目源代码

2.游戏房间

功能需求:进行五子棋的游戏。
思路分析:进入游戏房间的用户可以进行五子棋对局,前端使用画canvasAPI画布画出棋盘,然后在二维坐标上画子。前端点击棋盘的二维坐标,给后端发送落子响应(row:哪行,col:那列)给的后端,后端根据请求来判断对局是否结结束,然后将响应返回给两个前端,前端在画子。

1.约定前后端交互接口
websocket连接

ws://127.0.0.1:8080/game

连接响应:

{
    message: 'gameReady',    // 游戏就绪
    ok: true,                // 是否成功. 
    reason: '',                // 错误原因
    roomId: 'abcdef',        // 房间号. 用来辅助调试. 
    thisUserId: 1,            // 玩家自己的 id
    thatUserId: 2,            // 对手的 id
    whiteUser: 1,            // 先手方的 id
}

落子请求:

{
    message: 'putChess',
    userId: 1,
    row: 0,
    col: 0
}

落子响应:

{
    message: 'putChess',
    userId: 1,
    row: 0,
    col: 0
}

2.前端页面
核心:用 onclick 来处理用户点击事件. 当用户点击的时候通过这个函数来控制绘制棋子.
前端匹配成功后跳转到game_room.html页面

 chess.onclick = function (e) {
        if (over) {
            return;
        }
        if (!me) {
            return;
        }
        let x = e.offsetX;
        let y = e.offsetY;
        // 注意, 横坐标是列, 纵坐标是行
        let col = Math.floor(x / 30);
        let row = Math.floor(y / 30);
        if (chessBoard[row][col] == 0) {
            // 发送坐标给服务器, 服务器要返回结果
            send(row, col);


            // 留到浏览器收到落子响应的时候再处理(收到响应再来画棋子)
            // oneStep(col, row, gameInfo.isWhite);
            // chessBoard[row][col] = 1;
        }

    }

在 initGame 之前, 处理的是游戏就绪响应, 在收到游戏响应之后, 就改为接收落子响应了.
me 变量用来表示当前是否轮到我落子. over 变量用来表示游戏结束.

websocket.onmessage = function (event) {
    console.log('handlerPutChess: ' + event.data);

    let response = JSON.parse(event.data);
    if (response.message != 'putChess') {
        console.log('响应类型错误!');
        return;
    }

    // 1. 判断 userId 是自己的响应还是对方的响应, 
    //    以此决定当前这个子该画啥颜色的
    if (response.userId == gameInfo.thisUserId) {
        oneStep(response.col, response.row, gameInfo.isWhite);
    } else if (response.userId == gameInfo.thatUserId) {
        oneStep(response.col, response.row, !gameInfo.isWhite);
    } else {
        console.log('[putChess] response userId 错误! response=' + JSON.stringify(response));
        return;
    }
    chessBoard[response.row][response.col] = 1;
    me = !me; // 接下来该下个人落子了. 

    // 2. 判断游戏是否结束
    if (response.winner != 0) {
        // 胜负已分
        if (response.winner == gameInfo.thisUserId) {
            alert("你赢了!");
        } else {
            alert("你输了");
        }
        // 如果游戏结束, 则关闭房间, 回到游戏大厅. 
        location.assign('/game_hall.html')
    }

    // 3. 更新界面显示
    setScreenText(me);
}

3后端:
先引入了Room和RoomManager类,Room类用来进行一局游戏RoomManager来管理Room。
Room类,在匹配成功和挑战成功创建。
在这里插入图片描述

在这里插入图片描述
RoomManager类,用来管理room
在这里插入图片描述
GameAPI类websocket连接,处理游戏房间的逻辑
在两名玩家成功跳转到game——room页面后,再将两名玩家安排进房间,且先连接到后端的玩家被设置为先手在这里插入图片描述
在这里插入图片描述
handleTextMessage来处理前端的落子请求
在这里插入图片描述
putchess落子和判断胜负,然后将落子响应返回给前端
在这里插入图片描述
在这里插入图片描述

总结:游戏房间负责游戏进行,在匹配成功和挑战匹配之后,会创建room,在前端收到“gameReady”之后会跳转到game_room,这个时又会建立websocket连接,将玩家安排进房间里面,同时返回‘’gameReady‘’个前端,聊天功能的websocket连接也会用到这个“gameReady”。
具体源代码:码云项目源代码

3.挑战功能

功能需求:存在天梯积分top10排行榜,用户可以挑战排行榜上的在线用户,当然被挑战者也可以拒绝挑战。
思路分析
当前挑战者是lisi,如果他想挑zahnsgan,点击“挑战”需要给zhangsan发送挑战申请:挑战
zhangsan得能看到lisi的个人数据,然后选择接受/拒绝挑战

在这里插入图片描述

这就需要在点击“挑战”时,可以从前端获取到“挑战者”和“被挑战者”的信息,然后在后端将挑战请求转发给“被挑战者”,“被挑战者”的接受/拒绝也得通过后端转发给“挑战者”。

1.约定前后端交互接口
挑战者发出的请求
在这里插入图片描述
被挑战者受到的响应:
在这里插入图片描述
挑战者收到的响应:
在这里插入图片描述

2.前端代码
top10获取到分数前10的玩家,动态拼接前端标签。

 function top10(){
        jQuery.ajax({
            type: 'get',
            url: 'user/top10',
            data:{},
            success: function(result){
                //获取父标签scoreboard_list
                let scoreboard_listDiv = document.querySelector('.scoreboard_list');
                if(result!=null && result.code==200 && result.data.length>0){
                    // 清空父标签的子元素
                    scoreboard_listDiv.innerHTML = '';

                    //
                    for(var i= 0;i< result.data.length;i++){
                        //创建scoreboard_list_child标签
                        let scoreboard_list_childDiv = document.createElement('div');
                        // //标签设置class
                        scoreboard_list_childDiv.class = 'scoreboard_list_child';
                        //每一个玩家信息都在list里面
                        let usertop = result.data[i];
                        //判断用户当前的状态
                        let state = ''
                        if(usertop.state == 'no'){
                            state = '离线';
                        }else if(usertop.state == 'yes'){
                            state = '在线';
                        }else if(usertop.state == 'busy'){
                            state = '正在游戏中'
                        }
                        let userName = usertop.username;
                        let userState = usertop.state;
                        //拼标签
                        scoreboard_list_childDiv.innerHTML ='   <div class="scoreboard_list_child">\n' +
                            '                <div class="rank">'+(i+1)+'</div>'
                            +' <div class="username">'+usertop.username+'</div>'
                            +'<div class="score">'+usertop.score+'</div>'
                            +' <div class="state">'+state+'</div>'
                            + ' <button onClick="myfun(' + usertop.userId + ', \'' + userName + '\', \'' + userState + '\');challenge();" class="click" style="color:rgb(255, 133, 127);">挑战Ta </button>\n'
                            + '<div class="userid">'+usertop.userId+'</div>\n'+
                            '    </div>';

                        //
                        //子标签插入到父标签
                        scoreboard_listDiv.appendChild(scoreboard_list_childDiv);
                    }


                }else{
                    alert("用户信息异常!!!");
                }
            }
        })
    }

注意top10的调用时时机:
因为在游戏中的用户和离线的用户都不能被挑战,所以得确保积分榜上的用户数据是实时更新的,所以得在后端处理一下:当有用户在游戏大厅页面上下线时,当用户在进入/退出游戏页面时都调用一下top10,保证top10的数据是最新的
在这里插入图片描述
用户在游戏大厅上线
在这里插入图片描述
用户在游戏大厅下线
在这里插入图片描述
用户在游戏房间上线

在这里插入图片描述
用户在游戏大厅下线

在这里插入图片描述
前端判断调用top10

在这里插入图片描述
被挑战者接受挑战,挑战者跳转到游戏房间页面
在这里插入图片描述
被挑战者点击“接受挑战”会跳转到游戏房间页面
在这里插入图片描述

3后端代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结:挑战功能实际上是将两个与服务器websocket连接的玩家放入到同一个room里进行游戏,所以关键在于如何通过服务器把客户端的两个websocket连接的用户安排在一起,这就需要在发起挑战请求时就获取到挑战者和被挑战者的信息,这样在后端就可以通过userId,把两个用户的session关联在一起,就是在服务器与不同的前端存在大量的websocket连接下如何连接两个客户端的websocket连接。
在这里插入图片描述

具体源代码:码云项目源代码

4.人机对战

功能需求:可以挑战AI
思想分析:点击匹配按钮直接进入游戏房间,并且AI先手。
这里直接建立在ai游戏页面aigame的websocket连接,点击人机对战之后直接跳转ai游戏页面,AI先下手,人机的落子都在后端。
1.约定前后端交互接口
人机对战请求:
在这里插入图片描述
落子请求和响应
在这里插入图片描述
2.前端代码
前端注意以下over为true时不能在触发点击事件在棋盘上落子了。
在这里插入图片描述

在这里插入图片描述
3后端代码
AIGame里封装了人机对战的所有逻辑,包括:落子,打印棋盘,判断胜负,AI算生成落子坐标。
在这里插入图片描述

多线程环境下,每个玩家的人机对战得有独一无二的棋盘,不同的玩家之间不能相互影响,所以使用哈希表记录了每一个用户自己的AIGame在这里插入图片描述
算法的用的是五元组和一个积分统统计表,该算法会计算出棋盘上每个空位子处的分数,分数越高说明能赢的概率就越大,因此会根据棋盘上已有棋子,在分数在高的空位上落子。
在这里插入图片描述

总结:双人对局的简化版,在后端要处理好AI落子,然后在后端生成落子请求多用putchess,AI先落子,有个细节要处理一下:由于玩家发送一个落子请求之后,前端得收到玩家落子响应和AI落子响应,而玩家获胜之后,AI可以不用在继续落子了。
在这里插入图片描述

具体源代码:码云项目源代码

5.聊天功能

功能需求:在游戏界面里实时聊天。
思想分析:就是弄一个websocket连接,然后后端在转发消息时,可以精准的找到发送方和接收方的session进行转发即可。
1.前后端接口交互
在这里插入图片描述
2.前端代码
在这里插入图片描述

在这里插入图片描述

3.后端代码
在这里插入图片描述
在这里插入图片描述

总结:为了使前端发送的数据跟完整:有发送方和接收方,使用了“gameReady”里的数据,在后端进行转发时,借助了onlineUserManager和 roomManager来找消息接收方的会话。
具体源代码:码云项目源代码


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/908325.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java IO流(三)线程模型

传统阻塞I/O模式 其中黄色框表示对象,蓝色框表示线程,白色框表示API方法 特点 采用阻塞IO模式获取输入数据每个连接都需要独立的线程完成数据的输入,业务处理和处理结果数据返回 潜在问题 并发数很大时,需要对应每个连接请求创建一个线程,所以占用资源很大连接创建后,若当前…

CentOS系统环境搭建(十七)——elasticsearch设置密码

centos系统环境搭建专栏&#x1f517;点击跳转 elasticsearch设置密码 没有密码是很不安全的一件事&#x1f62d; 文章目录 elasticsearch设置密码1.设置密码2.登录elasticsearch3.登录kibana4.登录elasticsearch-head 1.设置密码 关于Elasticsearch的安装请看CentOS系统环境搭…

每天一道leetcode:1306. 跳跃游戏 III(图论中等广度优先遍历)

今日份题目&#xff1a; 这里有一个非负整数数组 arr&#xff0c;你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时&#xff0c;你可以跳到 i arr[i] 或者 i - arr[i]。 请你判断自己是否能够跳到对应元素值为 0 的 **任一** 下标处。 注意&#xff0c;不管是什…

【C++从0到王者】第二十二站:一文讲透多继承与菱形继承

文章目录 前言一、多继承二、菱形继承三、菱形虚拟继承四、菱形虚拟继承的底层原理五、菱形虚拟继承对于空间的优化六、多继承和菱形继承中的一些细节七、菱形继承在库里面的应用八、继承和组合九、继承总结 前言 在我们前面所说的继承其实在C中也叫做单继承 即一个子类只有一…

【Python爬虫案例】爬取大麦网任意城市的近期演出!

老规矩&#xff0c;先上结果&#xff1a; 含10个字段&#xff1a; 页码&#xff0c;演出标题&#xff0c;链接地址&#xff0c;演出时间&#xff0c;演出城市&#xff0c;演出地点&#xff0c;售价&#xff0c;演出类别&#xff0c;演出子类别&#xff0c;售票状态。 代码演示…

2023年清洁能源与智能电网国际会议(CCESG 2023)

会议简介 Brief Introduction 2023年清洁能源与智能电网国际会议(CCESG 2023) 会议时间&#xff1a;2023年 召开地点&#xff1a;中国南宁 大会官网&#xff1a;CCESG 2023-2023 International Joint Conference on Clean Energy and Smart Grid 由IASED主办&#xff0c; CoreS…

Linux的基础指令

目录 1、ls指令 .和..意义 2、pwd指令 3、cd指令 ①cd ~ ②cd - 关于cd ..的用法 绝对路径和相对路径 4、touch指令 5、mkdir指令 tree指令 6、rmdir指令 7、rm指令 * 8、man指令 9、cp指令 nano&#xff1a; 10、mv指令 11、cat指令 12、more指令 13、less…

【Python web细说实战】Django的集成测试

今天给大家分享一下Python Web开发——Django的集成测试&#xff0c;如何利用集成测试来提高代码质量、减少bug。 1. 什么是集成测试&#xff1f; 在开始介绍Django的集成测试之前&#xff0c;我们先来了解一下什么是集成测试。集成测试是软件开发中的一种测试方法&#xff0c…

HCIP——MPLS VPN实验

MPLS VPN 一、实验1、实验拓扑及要求2、实验拓扑搭建以及IP地址划分3、实验步骤1、配置永不超时以及接口IP地址2、给R1&#xff0c;R2&#xff0c;R3&#xff0c;R4配置OSPF3、配置BGP非直连建邻&#xff0c;并开启vpnV4路由传递功能4、配置MPLS5、在PE端做MPLS VPN6、给R1以及…

概率论与数理统计:第六章:数理统计

文章目录 Ch6. 数理统计(一) 总体与样本(二) 统计量 (5个)(三) 抽样分布 (3个)0.上α分位点1.χ分布2.t分布3.F分布 (四) 抽样分布定理1.单个正态总体2.两个正态总体 Ch6. 数理统计 (一) 总体与样本 1.概念&#xff1a; (1)总体 (2)样本 简单随机样本&#xff0c;简称样本。…

3种解决找不到mfc140u.dll无法继续执行代码的方法

1.解决mfc140u.dll丢失之前先了解一下它的一些具体功能和作用 用户界面和窗口管理&#xff1a;mfc140u.dll提供了一系列类和函数&#xff0c;用于创建和管理应用程序的用户界面元素&#xff0c;如窗口、菜单、工具栏、状态栏等。它还提供了窗口消息处理的机制&#xff0c;使开…

Hover研究: 下一代借贷协议 | 第一部分

这是我们对关于可持续通证经济模型用于借贷协议的研究&#xff0c;一共分为两部分&#xff0c;此篇为第一部分。 Hover团队花费了大量时间研究现有的借贷协议结构。我们的研究揭示了这些协议在扩展和增长过程中面临的多个挑战。 本系列将讨论这些挑战是什么&#xff0c;为什么它…

23款奔驰GLE350轿跑升级原厂前排座椅通风系统,夏天必备的功能

通风座椅的主动通风功能可以迅速将座椅表面温度降至适宜程度&#xff0c;从而确保最佳座椅舒适性。该功能启用后&#xff0c;车内空气透过打孔皮饰座套被吸入座椅内部&#xff0c;持续时间为 8 分钟。然后&#xff0c;风扇会自动改变旋转方向&#xff0c;将更凉爽的环境空气从座…

【业务功能篇72】分布式锁实现分析

什么是分布式锁&#xff1f;当多个进程在同一个系统中&#xff0c;用分布式锁控制多个进程对资源的访问 分布式锁应用场景 &#xff08;1&#xff09;传统的单体应用单机部署情况下&#xff0c;可以使用java并发处理相关的API进行互斥控制。 &#xff08;2&#xff09;分布式…

Qt下使用ModbusTcp通信协议进行PLC线圈/保持寄存器的读写(32位有符号数)

文章目录 前言一、引入Modbus模块二、Modbus设备的连接三、各寄存器数据的读取四、各寄存器数据的写入五、示例完整代码总结 前言 本文主要讲述了使用Qt的Modbus模块来进行ModbusTcp的通信&#xff0c;实现对PLC的线圈寄存器和保持寄存器的读写&#xff0c;基于TCP/IP的Modbus…

JavaSE【 String 类】

一、String 类 1、字符串构造 常用三种 String的构造方法&#xff1a;有带参数的&#xff0c;和不带参数的 public class Test {public static void main(String[] args) {//字符串直接赋值String str "hello";//string是一个引用类型&#xff0c;str这个变量存的…

【Redis】Redis 的学习教程(一)入门基础

1. 简介 Redis 全称&#xff1a;Remote Dictionary Server&#xff08;远程字典服务器&#xff09;&#xff0c;是一款开源的&#xff0c;遵守 BSD 协议&#xff0c;使用 C 语言开发的 key-value 存储系统。简单的说&#xff0c;它是一款跨平台的非关系型数据库&#xff0c;支…

第 111 场LeetCode 双周赛题解

A 统计和小于目标的下标对数目 数据量小&#xff0c;直接枚举数对 class Solution { public:int countPairs(vector<int> &nums, int target) {int n nums.size();int res 0;for (int i 0; i < n; i)for (int j 0; j < i; j)if (nums[i] nums[j] < tar…

day23 遍历 所有文件夹 子文件夹 文件

统计目录大小 public static long getAllFilesLength(File file) {long length 0l;if (file null) {System.out.println("文件为空");}long fileLengths[] new long[0];File files[] file.listFiles();for (int i 0; i < files.length; i) {if (files[i].isD…

用例图的基本概念及其使用方式(包含案例)

一、引言 用例(Use Case)&#xff0c;是软件工程或系统工程中对系统如何反应外界请求的描述&#xff0c;是一种通过用户的使用场景来获取需求的技术。此概念“用例”的提出者为Ivar Jacobson。每个用例提供了一个或多个场景&#xff0c;该场景说明了系统是如何和最终用户或其它…