用JavaScript实现找不同小游戏

目录

倒计时的实现

找不同实现

对canvas的初始化

实现画布的分割,  父盒子>子盒子>canvas

找不同逻辑的判断

实现图片的导入

 DOM节点的监听

声明两个需要监听的节点 

 禁用页面点击事件的函数

DOM监听  

 实现勋章分数增加和生命的减少

 减命逻辑

死亡响应

websocket交互

定义给后端发送的数据包

断线重连

最后,调用封装好的websocket创建


初始之物,其行必丑
代码写的非常繁琐,有很多细节在复盘的时候也会想不起来为什么会这样写,今天算是梳理一遍所有的代码,时隔多日又写一篇还是很激动的

此小游戏由两位前端,和一位后端共同完成,我主要说明我写的部分(初学者,还是很水的)

完整源码:khatung/find difference - Gitee.com

效果实现图如下:

用JavaScript实现找不同小游戏_第1张图片

 

倒计时的实现

  setTimeout(function () {
      let fill = document.querySelector('.fill');,这个就是进度条盒子(div),减少div长度达到进度条递减的效果
      console.log(fill);

      document.querySelector('.overlay').style.display = 'none'

      //执行回调函数
      var timer = setInterval(function () {
        // 检测网络连接状态
        if (!navigator.onLine) {
          // clearInterval(timer); // 停止倒计时
          document.addEventListener('click', disableClickEvent, true);
          alertT.style.display = 'block';
        } else {
          alertT.style.display = 'none';
        }

        if (countdown == 0) {
          clearInterval(timer);
          // alert('加载完成');
          data.type = "endGame"
          data.data.time = 0
          // data.time = 0;
          ws.send(JSON.stringify(data))

          return;
        } else {
          countdown--; // 每秒减少计时器的值
          fill.style.width = (parseFloat(fill.style.width) - 1.2) + 'vw'; // 将宽度属性转换为数值类型并递减

          j.innerHTML = countdown + "s";
        }
      }, 1000);
    }, 0);


  };

找不同实现

对canvas的初始化

我最开始不相信这么麻烦,尝试了很多别的方法都失败了,最后还是用canvas画布实现吧

实现画布的分割,  父盒子>子盒子>canvas
function createCanvasPiece(img, containerId) {
  let container = document.getElementById(containerId);
  //创建一个父盒子
  let canvasPieceContainer = document.createElement('div');
  canvasPieceContainer.classList.add('canvasPiece');

  for (let row = 0; row < 6; row++) {
    for (let col = 0; col < 6; col++) {
      // 创建一个新的画布元素
      let canvas = document.createElement('canvas');
      let divBox = document.createElement('div'); // 创建 div 盒子
      divBox.className = `canvas_${row}_${col}`; // 设置盒子的类名

      // canvas.width = 80; // 设置画布宽度为100像素
      // canvas.height = 50; // 设置画布高度为100像素
      let ctx = canvas.getContext('2d'); // 获取画布的2D上下文

      // 在画布上绘制图像的一部分
      ctx.drawImage(
        img, // 图像对象
        col * img.width / 6, // 源图像的起始水平位置
        row * img.height / 6, // 源图像的起始垂直位置
        img.width / 6, // 源图像的宽度
        img.height / 6, // 源图像的高度
        0, // 目标图像的起始水平位置
        0, // 目标图像的起始垂直位置
        300, // 目标图像的宽度290
        150 // 目标图像的高度180
      );

      // 添加类名到画布容器元素

      // 将画布元素添加到画布容器元素
      divBox.appendChild(canvas);

      // 将画布容器元素添加到父容器中
      canvasPieceContainer.appendChild(divBox);
    }
  }
  container.appendChild(canvasPieceContainer);  // let huoq = document.querySelector('.canvasPiece');
}
找不同逻辑的判断

对所有子盒子取类名,根据类名来判断哪些是不同的盒子(一共八处不同,当然为了更加精确可以继续细分canvas)

//添加事件监听
//标记八个盒子,禁止重复生成
var boxCreated1 = false; // 用于标记盒子是否已经创建过
var boxCreated2 = false; // 用于标记盒子是否已经创建过
var boxCreated3 = false; // 用于标记盒子是否已经创建过
var boxCreated4 = false; // 用于标记盒子是否已经创建过
var boxCreated5 = false; // 用于标记盒子是否已经创建过
var boxCreated6 = false; // 用于标记盒子是否已经创建过
var boxCreated7 = false; // 用于标记盒子是否已经创建过
var boxCreated8 = false; // 用于标记盒子是否已经创建过

function ab() {
  let playerDiv = document.querySelector('.canvasPiece');

  console.log(playerDiv)
  //鼠标监听事件
  function handleClickone(event) {
    //获取到canvas
    let clickedElement = event.target;
    // console.log(clickedElement + "1111111111111111111");
    //获取到canvas的父级
    let divBox = clickedElement.parentNode;
    console.log(divBox.className)

    console.log(1111)
    // 检查是否为特定子盒子元素
    if (divBox.className == 'canvas_0_1') {
      if (!boxCreated1) {
        let newBox = createBox(-4, 0);
        divBox.appendChild(newBox);
        boxCreated1 = true
        count++;
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else if (divBox.className == 'canvas_0_2') {
      if (!boxCreated2) {
        let newBox = createBox(3, 3);
        divBox.appendChild(newBox);
        boxCreated2 = true
        count++;
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else if (divBox.className == 'canvas_1_5') {
      if (!boxCreated3) {
        let newBox = createBox(0, -4);
        divBox.appendChild(newBox);
        boxCreated3 = true
        count++;
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else if (divBox.className == 'canvas_2_1') {
      if (!boxCreated4) {
        let newBox = createBox(-2.5, -3);
        divBox.appendChild(newBox);
        boxCreated4 = true
        count++;
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else if (divBox.className == 'canvas_2_3') {
      if (!boxCreated5) {
        let newBox = createBox(-6, -1);
        divBox.appendChild(newBox);
        boxCreated5 = true
        count++;
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else if (divBox.className == 'canvas_2_2') {
      if (!boxCreated5) {
        let newBox = createBox(7, -2);
        divBox.appendChild(newBox);
        boxCreated5 = true
        count++;
        // console.log(count)
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else if (divBox.className == 'canvas_3_4') {
      if (!boxCreated6) {
        let newBox = createBox(0, 0);
        divBox.appendChild(newBox);
        boxCreated6 = true
        count++;
        // console.log(count)
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else if (divBox.className == 'canvas_5_3') {
      if (!boxCreated7) {
        let newBox = createBox(-4, -5);
        divBox.appendChild(newBox);
        boxCreated7 = true
        count++;
        // console.log(count)
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else if (divBox.className == 'canvas_4_3') {
      if (!boxCreated7) {
        let newBox = createBox(-4, 4);
        divBox.appendChild(newBox);
        boxCreated7 = true
        count++;
        // console.log(count)
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else if (divBox.className == 'canvas_4_1') {
      if (!boxCreated8) {
        let newBox = createBox(2, -2);
        divBox.appendChild(newBox);
        boxCreated8 = true
        count++;
        // console.log(count)
        //调用添加标签函数
        addImageToLi(liElements2)
      }
    } else {
      var firstLi = HP.querySelector('li:first-child');
      if (firstLi) {
        HP.removeChild(firstLi);
      }
    }
  }
  // let playerDiv = document.querySelector('.canvasPiece');
  playerDiv.addEventListener('click', handleClickone);
}
实现图片的导入

因为只在右侧图片也就是img2上实现点击不同,所以需要先加载出img2,防止ab(); 方法被img1调用,导致显示在img上

//初始化图片一
function initializeCanvasPieces() {
  //必须先执行这个
  let img2 = new Image();
  img2.src = 'images/Gallery/greenHouse2.png';
  img2.onload = function () {
    console.log(111);
    createCanvasPiece(img2, 'dao2');
    ab();

  let img1 = new Image();
  img1.src = 'images/Gallery/greenHouse1.png';

  img1.onload = function () {
    createCanvasPiece(img1, 'dao1');
    };
  };
  
}

实现页面随机生成不同组的图片

//随机加载图片
window.addEventListener('load', function () {
  //根据当前分钟数来随机页面
  // 生成一个1到2之间的随机数
  var date = new Date();
  var minutes = date.getMinutes();
  var random = minutes % 2;  // 取分钟数的余数,结果为0或1  

  if (random === 1) {
    initializeCanvasPieces();  // 加载方法1
  } else {
    // 加载另一个方法
    initializeCanvasPieces_2(); //加载方法2
  }
});

 DOM节点的监听

当增加分数以后就得一分,添加一个盒子(勋章)

用JavaScript实现找不同小游戏_第2张图片

 具体实现如下:

声明两个需要监听的节点 
//命
const targetUL1 = document.querySelector('.three ul');
let liCount = targetUL1.querySelectorAll('li').length;;
//勋章
const targetUL2 = document.querySelector('.center ul');
let imgCount = targetUL2.querySelectorAll('li img').length;
 禁用页面点击事件的函数

这个函数调用在下面MutationObserver 中

function disableClickEvent(event) {
  event.stopPropagation();
  event.preventDefault();
}
DOM监听  

          创建了一个 MutationObserver 实例,并传入一个回调函数作为参数。当被观察的 DOM 节点发生变化时,MutationObserver 将调用这个回调函数,并传入一个 mutations 数组作为参数,该数组包含了所有发生的变化。
        在回调函数中,通过遍历 mutations 数组的每个 mutation 对象,可以获取有关 DOM 变化的详细信息。

// 创建 MutationObserver 实例,监听删除
const observer1 = new MutationObserver(function (mutations) {
  mutations.forEach(function (mutation) {
    if (mutation.removedNodes) {
      liCount = targetUL1.querySelectorAll('li').length;
      if (liCount === 0) {
        die.style.display = 'block'
        c = 22
        document.addEventListener('click', disableClickEvent, true);

      }
    }
  });
});
// 配置观察选项,并开始观察目标 UL 元素的子节点变化
const config1 = { childList: true };
observer1.observe(targetUL1, config1);



// 创建 MutationObserver 实例,监听删除
const observer2 = new MutationObserver(function (mutations) {
  mutations.forEach(function (mutation) {
    if (!mutation) {
      imgCount = 0
    }
    //结点的增加或删除
    if (mutation.addedNodes || mutation.removedNodes) {
      imgCount = targetUL2.querySelectorAll('li img').length;
      //分数增加,给后端发数据包
      console.log("分数增加")
      // 每更新一次就给后端发一次数据
      setLiCount();
      // console.log(imgCount+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
      //假如是最后一次,一个页面没有响应,此时,该页面还是最开始给后端发送的数据,此时后端知道了有一方完成了任务
      if (imgCount === 8) {
        data.type = "endGame"
        ws.send(JSON.stringify(data))
        // 跳转页面,存储数据到本地

        console.log(data)
      }
    }
  });
});

// 配置观察选项,并开始观察目标 UL 元素的子节点变化
const config2 = { childList: true, subtree: true };
observer2.observe(targetUL2, config2);

用JavaScript实现找不同小游戏_第3张图片

用JavaScript实现找不同小游戏_第4张图片

用JavaScript实现找不同小游戏_第5张图片

 实现勋章分数增加和生命的减少
//声明勋章,往盒子中添加勋章
var ulElement2 = document.querySelector('.own ul')
//获取li
var liElements2 = ulElement2.querySelectorAll('li');

//生命
var HP = document.querySelector('.three ul');


//创建添加img标签函数
function addImageToLi(liElements) {
  var firstLiWithoutImage = null;

  for (var i = 0; i < liElements.length; i++) {
    var liElement = liElements[i];
    var existingImage = liElement.querySelector('img');

    if (!existingImage) {
      firstLiWithoutImage = liElement;
      break; // 找到第一个没有  标签的 
  • 元素后跳出循环 } } if (firstLiWithoutImage) { var imgElement = document.createElement('img'); imgElement.src = './images/medal.png'; // 替换为你的勋章图片的 URL firstLiWithoutImage.appendChild(imgElement); } }
  •  减命逻辑

    用JavaScript实现找不同小游戏_第6张图片

    //创建盒子
    function createBox(leftPosition, topPosition) {
      var box = document.createElement('div');
      box.className = 'game-box';
      box.style.position = 'absolute';
      box.style.left = leftPosition + 'vh';
      box.style.top = topPosition + 'vh';
    
      return box;
    }
    死亡响应

    这个相当于添加一个死亡提示,当死亡以后此时出现游戏失败弹窗(div盒子),无法对页面进行任何操作(与上面的禁用页面点击事件一起实现效果),等待倒计时结束,双方同时跳转页面,并结算分数

    const die = document.querySelector('.die')
    die.addEventListener('click',function() {
      die.style.display = 'none'
    })

    websocket交互

    定义给后端发送的数据包
    var data = {
      type: "gameProgress",
      data: {
        username: found.data.username,
        userGender: found.data.userGender,
        uuid: found.data.uuid,
        score: imgCount,  //得分
        time: countdown, //倒计时
        userIcon: found.data.userIcon,
        roomId: found.data.roomId || ""
      }
    };
    
    // 自定义 setter 函数,在 liCount 变量的值改变时发送数据给后端
    function setLiCount() {
      //获取本地存储
      data = {
        type: "gameProgress",
        data: {
          username: found.data.username,
          userGender: found.data.userGender,
          uuid: found.data.uuid,
          score: imgCount,  //得分
          time: countdown, //倒计时
          userIcon: found.data.userIcon,
          roomId: found.data.roomId || ""
        }
      };

    定义临时存储

    一般地,当我修改数据了以后,就将数据存储到本地存储,再将本地存储中的数据发送给后端,这样不容易混乱

    let found = JSON.parse(sessionStorage.getItem('data'));
    

     创建websocket连接

    //创建连接
    function createWebSocket(url) {
      try {
        ws = new WebSocket(url);
        initEventHandle();
      } catch (e) {
        reconnect(url);
      }
    }
    //连接
    function initEventHandle() {
    
      // 监听 WebSocket 连接关闭事件
      ws.onclose = function () {
        console.log("WebSocket连接已关闭");
        // 停止倒计时
        // clearInterval(timer);
        //存储data数据包(只需要分数即可)
        sessionStorage.setItem("data", JSON.stringify(data))
        reconnect(wsUrl);
      };
    
      //连接异常 - 重连
      ws.onerror = function (event) {
        console.log('onerror:' + event.data);
        reconnect(wsUrl);
      };
    
      // 建立连接
      ws.onopen = function () {
        // WebSocket 连接已建立
        console.log('链接已建立')
    
    
        // 获取存储在 sessionStorage 中的数据
        var gai = JSON.parse(sessionStorage.getItem('data'));
        // 修改数据
         gai.type = "updateConn";
         console.log(gai)
    
       // 将修改后的数据存储回 sessionStorage 中
        sessionStorage.setItem('data', JSON.stringify(gai));
    
    
        ws.send(sessionStorage.getItem('data'))
    
        //如果是重新连接
        // if(){
    
        //   ws.send(sessionStorage.getItem('data'))
        // }
        //保持连接:每5秒发送一个消息
    
    
        //发送心跳检测前判断链接状态
        setInterval(function () {
          ws.send(JSON.stringify({
            "type": "check",
            "data": {
            }
          }));
        }, 5000);
    
    
    // 监听后端发送的消息,只有第一次和最后一次
      ws.onmessage = function (event) {
    
        var receivedData = event.data;
        console.log("接收到后端发送的数据:", JSON.parse(receivedData));
        const real = JSON.parse(receivedData)
        if (real == "check") {
          console.log("连上了,哥")
        // 启用页面点击事件
        if(c  == 11){
          //如果没死还能点
          document.removeEventListener('click', disableClickEvent, true);
        }
    
        } else {
          
        
          // 将两方最后的数据转换为 JSON 字符串并存储到 sessionStorage
          //更新时间
          data.data.time = countdown
          var jsonData = JSON.stringify(data);
    
          sessionStorage.setItem("data", jsonData);
    
          console.log(jsonData)
          if (real.type == "endGame") {
            //本地存储json字符串
            console.log("213333333333333333333333333333")
            //接受对方的数据
            if (real.code == "200") {
              //本地存储,发送数据包
              // 跳转页面
              ws.send(sessionStorage.getItem('data'))
              window.location.href = '../doubleEnd/end2.html'; // 修改为你要跳转的页面的 URL
            }
          }
    
    
          //从后端获取信息,渲染数据
          let roomId = real.data.roomId;
          let username = real.data.username;
          let score = real.data.score;
          console.log(score)
    
          console.log("roomId", roomId);
          if (enemy) {
            enemy.innerHTML = `敌方得分:${score}`;  //渲染敌方分数
             //如果增加分数肯定调用增加盒子函数
             if(score ==1 && !boxCreat1){
              addImageToLi(liElements3);
              boxCreat1 =true
             }else if(score ==2 && !boxCreat2){
              addImageToLi(liElements3);
              boxCreat2 =true
             } else if(score ==3 && !boxCreat3){
              addImageToLi(liElements3);
              boxCreat3 =true
             } else if(score ==4 && !boxCreat4){
              addImageToLi(liElements3);
              boxCreat4 =true
             } else if(score ==5 && !boxCreat5){
              addImageToLi(liElements3);
              boxCreat5 =true
             } else if(score ==6 && !boxCreat6){
              addImageToLi(liElements3);
              boxCreat6 =true
             } else if(score ==7 && !boxCreat7){
              addImageToLi(liElements3);
              boxCreat7 =true
             } else if(score ==8 && !boxCreat8){
              addImageToLi(liElements3);
              boxCreat8 =true
             }
    
          }
          console.log(score + "========")
    
          console.log(real + "hhhhhhhhhhhhhh");
    
        }
      };
    }
    
    断线重连
    //重连操作
    function reconnect(url) {
      if (lockReconnect) {
        return;
      }
      lockReconnect = true;
      //没连接上会一直重连,设置延迟避免请求过多
      setTimeout(function () {
        createWebSocket(url);
        lockReconnect = false;
      }, 3000); //5秒后重连
    }
    最后,调用封装好的websocket创建
    //调用websocket
    createWebSocket(wsUrl);

    你可能感兴趣的:(javascript,开发语言,前端,websocket)