• js实现简单的俄罗斯方块小游戏


    js实现简单的俄罗斯方块小游戏

    开始

    1. 创建一个宽为 200px,高为 360px 的背景容器

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>俄罗斯方块</title>
      <style>
        .container {
          position: relative;
           200px;
          height: 360px;
          background-color: #000;
        }
      </style>
    </head>
    
    <body>
      <!-- 背景容器 -->
      <div class="container"></div>
    </body>
    
    </html>
    

    2. 在该容器上创建一个 20 * 20 的块元素

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>俄罗斯方块</title>
      <style>
        .container {
          position: relative;
           200px;
          height: 360px;
          background-color: #000;
        }
    
        .activity-model {
           20px;
          height: 20px;
          background-color: cadetblue;
          border: 1px solid #eeeeee;
          box-sizing: border-box;
          position: absolute;
        }
      </style>
    </head>
    
    <body>
      <!-- 背景容器 -->
      <div class="container">
        <!-- 块元素 -->
        <div class="activity-model"></div>
      </div>
    </body>
    
    </html>
    

    3. 控制该元素的移动,每次移动 20px

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>俄罗斯方块</title>
      <style>
        .container {
          position: relative;
           200px;
          height: 360px;
          background-color: #000;
        }
    
        .activity-model {
           20px;
          height: 20px;
          background-color: cadetblue;
          border: 1px solid #eeeeee;
          box-sizing: border-box;
          position: absolute;
        }
      </style>
    </head>
    
    <body>
      <!-- 背景容器 -->
      <div class="container">
        <!-- 块元素 -->
        <div class="activity-model"></div>
      </div>
      <script>
        // 常量
        // 每次移动的距离 步长
        const STEP = 20
    
        init()
        // 入口方法
        function init() {
          onKeyDown()
        }
    
        // 监听用户的键盘事件
        function onKeyDown() {
          document.onkeydown = event => {
            switch (event.keyCode) {
              case 38: // 上
                move(0, -1)
                break;
              case 39: // 右
                move(1, 0)
                break;
              case 40: // 下
                move(0, 1)
                break;
              case 37: // 左
                move(-1, 0)
                break;
              default:
                break;
            }
          }
        }
    
        // 移动
        function move(x, y) {
          // 控制块元素进行移动
          const activityModelEle = document.getElementsByClassName("activity-model")[0]
          activityModelEle.style.top = parseInt(activityModelEle.style.top || 0) + y * STEP + "px"
          activityModelEle.style.left = parseInt(activityModelEle.style.left || 0) + x * STEP + "px"
        }
      </script>
    </body>
    
    </html>
    

    构建 L 形状的模型

    1. 将容器进行分割,分割为 18 行,10 列。行高,列高均为20

    // 常量
    // 每次移动的距离 步长
    const STEP = 20
    // 分割容器
    // 18行 10列
    const ROW_COUNT = 18, COL_COUNT = 10
    

    2. 以 16宫格 为基准,定义 L 形状的 4 个方块的位置

    // 分割容器
    // 18行 10列
    const ROW_COUNT = 18, COL_COUNT = 10
    // 创建每个模型的数据源
    const MODELS = [
      // 第1个模型数据源(L型)
      {
        0: {
          row: 2,
          col: 0
        },
        1: {
          row: 2,
          col: 1
        },
        2: {
          row: 2,
          col: 2
        },
        3: {
          row: 1,
          col: 2
        }
      }]
    

    3. 创建 L 型模型,根据 16 宫格中的数据将模型渲染到页面上

    // 分割容器
    // 18行 10列
    const ROW_COUNT = 18, COL_COUNT = 10
    // 创建每个模型的数据源
    const MODELS = [
      // 第1个模型数据源(L型)
      {
        0: {
          row: 2,
          col: 0
        },
        1: {
          row: 2,
          col: 1
        },
        2: {
          row: 2,
          col: 2
        },
        3: {
          row: 1,
          col: 2
        }
      }]
    // 变量
    // 当前使用的模型
    let currentModel = {}
    init()
    // 入口方法
    function init() {
      createModel()
      onKeyDown()
    }
    
    // 根据模型使用的数据创建对应的块元素
    function createModel() {
      // 确定当前使用哪一个模型
      currentModel = MODELS[0]
      // 生成对应数量的块元素
      for (const key in currentModel) {
        const divEle = document.createElement('div')
        divEle.className = "activity-model"
        document.getElementById("container").appendChild(divEle)
      }
      // 定位块元素位置
      locationBlocks()
    }
    
    // 根据数据源定位块元素的位置
    function locationBlocks() {
      // 1 拿到所有的块元素
      const eles = document.getElementsByClassName("activity-model")
      for (let i = 0; i < eles.length; i++) {
        // 单个块元素
        const activityModelEle = eles[i]
        // 2 找到每个块元素对应的数据 (行、列)
        const blockModel = currentModel[i]
        // 3 根据每个块元素对应的数据来指定块元素的位置
        activityModelEle.style.top = blockModel.row * STEP + "px"
        activityModelEle.style.left = blockModel.col * STEP + "px"
      }
    }
    

    控制该模型进行移动

    • 本质是控制 16 宫格 进行移动

    // 根据数据源定位块元素的位置
    function locationBlocks() {
      // 1 拿到所有的块元素
      const eles = document.getElementsByClassName("activity-model")
      for (let i = 0; i < eles.length; i++) {
        // 单个块元素
        const activityModelEle = eles[i]
        // 2 找到每个块元素对应的数据 (行、列)
        const blockModel = currentModel[i]
        // 3 根据每个块元素对应的数据来指定块元素的位置
        // 每个块元素的位置由2个值确定:
        //  a. 16 宫格所在的位置
        //  b. 块元素在 16 宫格中的位置 
        activityModelEle.style.top = (currentY + blockModel.row) * STEP + "px"
        activityModelEle.style.left = (currentX + blockModel.col) * STEP + "px"
      }
    }
    
    // 移动
    function move(x, y) {
      // 控制16宫格元素进行移动
      currentX += x
      currentY += y
      // 根据16宫格的位置来重新定位块元素
      locationBlocks()
    }
    

    控制模型旋转

    规律

    • 以 16宫格 的中心点为基准进行旋转

    • 观察上图中旋转后每个块元素发生的位置的变化

    • 以第1,2个L模型为例,可以观察到:…

      • 块元素1的坐标(列, 行)变化:(0, 2) -> (1, 0)
      • 块元素2的坐标(列, 行)变化:(1, 2) -> (1, 1)
      • 块元素3的坐标(列, 行)变化:(2, 2) -> (1, 2)
      • 块元素4的坐标(列, 行)变化:(2, 1) -> (2, 2)
    • 其基本变化规律是

      • 移动后的行 = 移动前的列
      • 移动后的列 = 3 - 移动前的行

    旋转模型

    // 监听用户的键盘事件
    function onKeyDown() {
      document.onkeydown = event => {
        switch (event.keyCode) {
          case 38: // 上
            // move(0, -1)
            rotate()
            break;
          case 39: // 右
            move(1, 0)
            break;
          case 40: // 下
            move(0, 1)
            break;
          case 37: // 左
            move(-1, 0)
            break;
          default:
            break;
        }
      }
    }
    
    // 旋转模型
    function rotate() {
      // 算法
      // 旋转后的行 = 旋转前的列
      // 旋转后的列 = 3 - 旋转前的行
    
      // 遍历模型数据源
      for (const key in currentModel) {
        // 块元素的数据
        const blockModel = currentModel[key]
        // 实现算法
        let temp = blockModel.row
        blockModel.row = blockModel.col
        blockModel.col = 3 - temp
      }
      locationBlocks()
    }
    

    控制模型只在容器中移动

    // 根据数据源定位块元素的位置
    function locationBlocks() {
      // 判断一下块元素的越界行为
      checkBound()
      // 1 拿到所有的块元素
      const eles = document.getElementsByClassName("activity-model")
      for (let i = 0; i < eles.length; i++) {
        // 单个块元素
        const activityModelEle = eles[i]
        // 2 找到每个块元素对应的数据 (行、列)
        const blockModel = currentModel[i]
        // 3 根据每个块元素对应的数据来指定块元素的位置
        // 每个块元素的位置由2个值确定:
        //  a. 16 宫格所在的位置
        //  b. 块元素在 16 宫格中的位置 
        activityModelEle.style.top = (currentY + blockModel.row) * STEP + "px"
        activityModelEle.style.left = (currentX + blockModel.col) * STEP + "px"
      }
    }
    
    // 控制模型只能在容器中
    function checkBound() {
      // 定义模型可以活动的边界
      let leftBound = 0, rightBound = COL_COUNT, bottomBound = ROW_COUNT
      // 当块元素超出了边界之后,让 16 宫格后退1格
      for (const key in currentModel) {
        // 块元素的数据
        const blockModel = currentModel[key]
        // 左侧越界
        if ((blockModel.col + currentX) < 0) {
          currentX++
        }
        // 右侧越界
        if ((blockModel.col + currentX) >= rightBound) {
          currentX--
        }
        // 底部越界
        if ((blockModel.row + currentY) >= bottomBound) {
          currentY--
        }
      }
    }
    

    当模型触底时,将块元素变为灰色固定在底部,同时生成一个新的模型

    声明样式类

    .fixed-model {
       20px;
      height: 20px;
      background-color: #fefefe;
      border: 1px solid #333333;
      box-sizing: border-box;
      position: absolute;
    }
    

    触底时固定,生成新模型

    • 需要注意的是:当模型触底被固定后,我们需要重新再生成一个新的模型,再生成新模型的时候,需要重置 16宫格 的位置,否则新创建的模型的位置会出现在底部,并将上一模型覆盖掉

    // 根据模型使用的数据创建对应的块元素
    function createModel() {
      // 确定当前使用哪一个模型
      currentModel = MODELS[0]
      // 重置16宫格的位置
      currentY = 0
      currentY = 0
      // 生成对应数量的块元素
      for (const key in currentModel) {
        const divEle = document.createElement('div')
        divEle.className = "activity-model"
        document.getElementById("container").appendChild(divEle)
      }
      // 定位块元素位置
      locationBlocks()
    }
    
    // 控制模型只能在容器中
    function checkBound() {
      // 定义模型可以活动的边界
      let leftBound = 0, rightBound = COL_COUNT, bottomBound = ROW_COUNT
      // 当块元素超出了边界之后,让 16 宫格后退1格
      for (const key in currentModel) {
        // 块元素的数据
        const blockModel = currentModel[key]
        // 左侧越界
        if ((blockModel.col + currentX) < 0) {
          currentX++
        }
        // 右侧越界
        if ((blockModel.col + currentX) >= rightBound) {
          currentX--
        }
        // 底部越界
        if ((blockModel.row + currentY) >= bottomBound) {
          currentY--
          fixedBottomModel() // 把模型固定在底部
        }
      }
    }
    
    // 把模型固定在底部
    function fixedBottomModel() {
      // 1 改变模型的样式
      // 2 禁止模型再进行移动
      const activityModelEles = document.getElementsByClassName('activity-model')
        ;[...activityModelEles].forEach((ele, i) => {
          // 更改块元素类名
          ele.className = "fixed-model"
          // 把该块元素放入变量中
          const blockModel = currentModel[i]
          fixedBlocks[`${currentY + blockModel.row}_${currentX + blockModel.col}`] = ele
        })
      // 3 创建新的模型
      createModel()
    }
    

    判断块元素与块元素之间的碰撞,分为左右接触底部接触

    记录所有块元素的位置

    // 记录所有块元素的位置
    // key=行_列 : V=块元素
    const fixedBlocks = {}
    

    当块元素被固定到底部的时候,将块元素存储在fixedBlocks 中

    // 把模型固定在底部
    function fixedBottomModel() {
      // 1 改变模型的样式
      // 2 禁止模型再进行移动
      const activityModelEles = document.getElementsByClassName('activity-model')
        ;[...activityModelEles].forEach((ele, i) => {
          // 更改块元素类名
          ele.className = "fixed-model"
          // 把该块元素放入变量中
          const blockModel = currentModel[i]
          fixedBlocks[`${currentY + blockModel.row}_${currentX + blockModel.col}`] = ele
        })
      // 3 创建新的模型
      createModel()
    }
    

    处理模型之间的碰撞(左右接触)

    // 移动
    function move(x, y) {
      // 16宫格移动
      if (isMeet(currentX + x, currentY + y, currentModel)) {
        return
      }
      currentX += x
      currentY += y
      // 根据16宫格的位置来重新定位块元素
      locationBlocks()
    }
    
    // 旋转模型
    function rotate() {
      // 算法
      // 旋转后的行 = 旋转前的列
      // 旋转后的列 = 3 - 旋转前的行
    
      // 克隆一下 currentModel 深拷贝
      const cloneCurrentModel = JSON.parse(JSON.stringify(currentModel))
    
      // 遍历模型数据源
      for (const key in cloneCurrentModel) {
        // 块元素的数据
        const blockModel = cloneCurrentModel[key]
        // 实现算法
        let temp = blockModel.row
        blockModel.row = blockModel.col
        blockModel.col = 3 - temp
      }
      // 如果旋转之后会发生触碰,那么就不需要进行旋转了
      if (isMeet(currentX, currentY, cloneCurrentModel)) {
        return
      }
      // 接受了这次旋转
      currentModel = cloneCurrentModel
      locationBlocks()
    }
    
    // 判断模型之间的触碰问题
    // x, y 表示16宫格《将要》移动的位置
    // model 表示当前模型数据源《将要》完成的变化
    function isMeet(x, y, model) {
      // 所谓模型之间的触碰,在一个固定的位置已经存在一个被固定的块元素时,那么
      // 活动中的模型不可以再占用该位置
      // 判断触碰,就是在判断活动中的模型《将要移动到的位置》是否已经存在被固定
      // 的块元素
      // 返回 true 表示将要移动到的位置会发生触碰 否则返回 false
      for (const key in model) {
        const blockModel = model[key]
        // 该位置是否已经存在块元素?
        if (fixedBlocks[`${y + blockModel.row}_${x + blockModel.col}`]) {
          return true
        }
      }
      return false
    }
    

    处理模型之间的碰撞(底部接触)

    // 移动
    function move(x, y) {
      if (isMeet(currentX + x, currentY + y, currentModel)) {
        // 底部的触碰发生在移动16宫格的时候,并且此次移动是因为 Y 轴的变化而引起的
        if (y != 0) {
          // 模型之间发生触碰了
          fixedBottomModel()
        }
        return
      }
      // 控制16宫格元素进行移动
      currentX += x
      currentY += y
      // 根据16宫格的位置来重新定位块元素
      locationBlocks()
    }
    

    处理被铺满的行

    判断一行是否被铺满

    // 把模型固定在底部
    function fixedBottomModel() {
      // 1 改变模型的样式
      // 2 禁止模型再进行移动
      const activityModelEles = document.getElementsByClassName('activity-model')
        ;[...activityModelEles].forEach((ele, i) => {
          ele.className = "fixed-model"
          // 把该块元素放入变量中
          const blockModel = currentModel[i]
          fixedBlocks[`${currentY + blockModel.row}_${currentX + blockModel.col}`] = ele
        })
      // 判断某一行是否要清理
      isRemoveLine()
      // 3 创建新的模型
      createModel()
    }
    
    // 判断一行是否被铺满
    function isRemoveLine() {
      // 在一行中,每一列都存在块元素,那么该行就需要被清理了
      // 遍历所有行中的所有列
      // 遍历所有行
      for (let i = 0; i < ROW_COUNT; i++) {
        // 标记符 假设当前行已经被铺满了
        let flag = true
        // 遍历当前行中的所有列
        for (let j = 0; j < COL_COUNT; j++) {
          // 如果当前行中有一列没有数据,那么就说明当前行没有被铺满
          if (!fixedBlocks[`${i}_${j}`]) {
            flag = false
            break
          }
        }
        if (flag) {
          //  该行已经被铺满了
          console.log("该行已经被铺满了")
        }
      }
    }
    

    清理被铺满的一行

    function isRemoveLine() {
      // 在一行中,每一列都存在块元素,那么该行就需要被清理了
      // 遍历所有行中的所有列
      // 遍历所有行
      for (let i = 0; i < ROW_COUNT; i++) {
        // 标记符 假设当前行已经被铺满了
        let flag = true
        // 遍历当前行中的所有列
        for (let j = 0; j < COL_COUNT; j++) {
          // 如果当前行中有一列没有数据,那么就说明当前行没有被铺满
          if (!fixedBlocks[`${i}_${j}`]) {
            flag = false
            break
          }
        }
        if (flag) {
          //  该行已经被铺满了
          removeLine(i)
        }
      }
    }
    
    // 清理被铺满的这一行
    function removeLine(line) {
      // 1 删除该行中所有的块元素
      // 2 删除该行所有块元素的数据源
      // 遍历该行中的所有列
      for (let i = 0; i < COL_COUNT; i++) {
        // 1 删除该行中所有的块元素
        document.getElementById("container").removeChild(fixedBlocks[`${line}_${i}`])
        // 2 删除该行所有块元素的数据源
        fixedBlocks[`${line}_${i}`] = null
      }
    }
    

    让被清理行之上的块元素下落

    // 清理被铺满的这一行
    function removeLine(line) {
      // 1 删除该行中所有的块元素
      // 2 删除该行所有块元素的数据源
      // 遍历该行中的所有列
      for (let i = 0; i < COL_COUNT; i++) {
        // 1 删除该行中所有的块元素
        document.getElementById("container").removeChild(fixedBlocks[`${line}_${i}`])
        // 2 删除该行所有块元素的数据源
        fixedBlocks[`${line}_${i}`] = null
      }
      downLine(line)
    }
    
    // 让被清理行之上的块元素下落
    function downLine(line) {
      // 1 被清理行之上的所有块元素数据源所在行数 + 1
      // 2 让块元素在容器中的位置下落
      // 3 清理之前的块元素
      // 遍历被清理行之上的所有行
      for (let i = line - 1; i >= 0; i--) {
        // 该行中的所有列
        for (let j = 0; j < COL_COUNT; j++) {
          if (!fixedBlocks[`${i}_${j}`]) continue
          // 存在数据
          // 1 被清理行之上的所有块元素数据源所在行数 + 1
          fixedBlocks[`${i + 1}_${j}`] = fixedBlocks[`${i}_${j}`]
          // 2 让块元素在容器中的位置下落
          fixedBlocks[`${i + 1}_${j}`].style.top = (i + 1) * STEP + "px"
          // 3 清理之前的块元素
          fixedBlocks[`${i}_${j}`] = null
        }
      }
    }
    

    创建多种模型样式

    定义模型样式

     // 创建每个模型的数据源
    const MODELS = [
      // 第1个模型数据源(L型)
      {
        0: {
          row: 2,
          col: 0
        },
        1: {
          row: 2,
          col: 1
        },
        2: {
          row: 2,
          col: 2
        },
        3: {
          row: 1,
          col: 2
        }
      },
      // 第2个模型数据源(凸)
      {
        0: {
          row: 1,
          col: 1
        },
        1: {
          row: 0,
          col: 0
        },
        2: {
          row: 1,
          col: 0
        },
        3: {
          row: 2,
          col: 0
        }
      },
      // 第3个模型数据源(田)
      {
        0: {
          row: 1,
          col: 1
        },
        1: {
          row: 2,
          col: 1
        },
        2: {
          row: 1,
          col: 2
        },
        3: {
          row: 2,
          col: 2
        }
      },
      // 第4个模型数据源(一)
      {
        0: {
          row: 0,
          col: 0
        },
        1: {
          row: 0,
          col: 1
        },
        2: {
          row: 0,
          col: 2
        },
        3: {
          row: 0,
          col: 3
        }
      },
      // 第5个模型数据源(Z)
      {
        0: {
          row: 1,
          col: 1
        },
        1: {
          row: 1,
          col: 2
        },
        2: {
          row: 2,
          col: 2
        },
        3: {
          row: 2,
          col: 3
        }
      }
    ]
    

    创建模型的时候随机选取不同的模型样式

    // 根据模型使用的数据创建对应的块元素
    function createModel() {
      // 确定当前使用哪一个模型
      const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之间的随机数
      currentModel = MODELS[randow]
      // 重置16宫格的位置
      currentY = 0
      currentY = 0
      // 生成对应数量的块元素
      for (const key in currentModel) {
        const divEle = document.createElement('div')
        divEle.className = "activity-model"
        document.getElementById("container").appendChild(divEle)
      }
      // 定位块元素位置
      locationBlocks()
    }
    

    模型自动降落

    // 定时器
    let mInterval = null
        
    // 根据模型使用的数据创建对应的块元素
    function createModel() {
      // 确定当前使用哪一个模型
      // 确定当前使用哪一个模型
      const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之间的随机数
      currentModel = MODELS[randow]
      // 重置16宫格的位置
      currentY = 0
      currentY = 0
      // 生成对应数量的块元素
      for (const key in currentModel) {
        const divEle = document.createElement('div')
        divEle.className = "activity-model"
        document.getElementById("container").appendChild(divEle)
      }
      // 定位块元素位置
      locationBlocks()
      // 模型自动下落
      autoDown()
    }
    
    // 让模型自动下落
    function autoDown() {
      if (mInterval) {
        clearInterval(mInterval)
      }
      mInterval = setInterval(() => {
        move(0, 1)
      }, 600)
    }
    

    游戏结束

    判断游戏结束

    // 根据模型使用的数据创建对应的块元素
    function createModel() {
      // 判断游戏是否结束
      if (isGameOver()) {
        console.log("游戏结束!")
        return
      }
      // 确定当前使用哪一个模型
      const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之间的随机数
      currentModel = MODELS[randow]
      // 重置16宫格的位置
      currentY = 0
      currentY = 0
      // 生成对应数量的块元素
      for (const key in currentModel) {
        const divEle = document.createElement('div')
        divEle.className = "activity-model"
        document.getElementById("container").appendChild(divEle)
      }
      // 定位块元素位置
      locationBlocks()
      // 模型自动下落
      autoDown()
    }
    
    // 判断游戏结束
    function isGameOver() {
      // 当第0行存在块元素的时候,表示游戏结束了
      for (let i = 0; i < COL_COUNT; i++) {
        if (fixedBlocks[`0_${i}`]) return true
      }
      return false
    }
    

    结束游戏

    // 根据模型使用的数据创建对应的块元素
    function createModel() {
      // 判断游戏是否结束
      if (isGameOver()) {
        gameOver() // 结束游戏
        return
      }
      // 确定当前使用哪一个模型
      const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之间的随机数
      currentModel = MODELS[randow]
      // 重置16宫格的位置
      currentY = 0
      currentY = 0
      // 生成对应数量的块元素
      for (const key in currentModel) {
        const divEle = document.createElement('div')
        divEle.className = "activity-model"
        document.getElementById("container").appendChild(divEle)
      }
      // 定位块元素位置
      locationBlocks()
      // 模型自动下落
      autoDown()
    }
    
    // 结束掉游戏
    function gameOver() {
      // 1 停止定时器
      if (mInterval) {
        clearInterval(mInterval)
      }
      // 2 弹出对话框
      alert("大吉大利,今晚吃鸡!")
    }
    

    扩展:计分 + 最高分 + 重新开始游戏

    结构 + 样式

    body {
      display: flex;
    }
    #scores {
      margin-left: 20px;
    }
    
    <!-- 背景容器 -->
    <div id="container" class="container">
      <!-- 块元素 -->
      <!-- <div class="activity-model"></div> -->
    </div>
    <div id="scores">
      <p>最高分:<span id="max-score">0</span></p>
      <p>分数:<span id="current-score">0</span></p>
      <button onclick="reset()">重新开始</button>
    </div>

    逻辑

    // 最高分
    let maxScore = 0
    // 当前分数
    let score = 0
    
    // 清理被铺满的这一行
    function removeLine(line) {
      // 1 删除该行中所有的块元素
      // 2 删除该行所有块元素的数据源
      // 遍历该行中的所有列
      for (let i = 0; i < COL_COUNT; i++) {
        // 1 删除该行中所有的块元素
        document.getElementById("container").removeChild(fixedBlocks[`${line}_${i}`])
        // 2 删除该行所有块元素的数据源
        fixedBlocks[`${line}_${i}`] = null
      }
      // 更新当前分数
      score += COL_COUNT
      document.getElementById("current-score").innerHTML = score
      downLine(line)
    }
        
    // 结束掉游戏
    function gameOver() {
      // 1 停止定时器
      if (mInterval) {
        clearInterval(mInterval)
      }
      //  重置最高分数
      maxScore = Math.max(maxScore, score)
      document.getElementById("max-score").innerHTML = maxScore
      // 2 弹出对话框
      alert("大吉大利,今晚吃鸡!")
    }
    
    // 重新开始
    function reset() {
      const container = document.getElementById("container")
      const childs = container.childNodes;
      for (let i = childs.length - 1; i >= 0; i--) {
        container.removeChild(childs[i]);
      }
      fixedBlocks = {}
      score = 0
      document.getElementById("current-score").innerHTML = score
      init()
    }
    

    完整代码

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>俄罗斯方块</title>
      <style>
        body {
          display: flex;
        }
    
        .container {
          position: relative;
           200px;
          height: 360px;
          background-color: #000;
        }
    
        .activity-model {
           20px;
          height: 20px;
          background-color: cadetblue;
          border: 1px solid #eeeeee;
          box-sizing: border-box;
          position: absolute;
        }
    
        .fixed-model {
           20px;
          height: 20px;
          background-color: #fefefe;
          border: 1px solid #333333;
          box-sizing: border-box;
          position: absolute;
        }
    
        #scores {
          margin-left: 20px;
        }
      </style>
    </head>
    
    <body>
      <!-- 背景容器 -->
      <div id="container" class="container">
        <!-- 块元素 -->
        <!-- <div class="activity-model"></div> -->
      </div>
      <div id="scores">
        <p>最高分:<span id="max-score">0</span></p>
        <p>分数:<span id="current-score">0</span></p>
        <button onclick="reset()">重新开始</button>
      </div>
      <script>
        // 常量
        // 每次移动的距离 步长
        const STEP = 20
        // 分割容器
        // 18行 10列
        const ROW_COUNT = 18, COL_COUNT = 10
        // 创建每个模型的数据源
        const MODELS = [
          // 第1个模型数据源(L型)
          {
            0: {
              row: 2,
              col: 0
            },
            1: {
              row: 2,
              col: 1
            },
            2: {
              row: 2,
              col: 2
            },
            3: {
              row: 1,
              col: 2
            }
          },
          // 第2个模型数据源(凸)
          {
            0: {
              row: 1,
              col: 1
            },
            1: {
              row: 0,
              col: 0
            },
            2: {
              row: 1,
              col: 0
            },
            3: {
              row: 2,
              col: 0
            }
          },
          // 第3个模型数据源(田)
          {
            0: {
              row: 1,
              col: 1
            },
            1: {
              row: 2,
              col: 1
            },
            2: {
              row: 1,
              col: 2
            },
            3: {
              row: 2,
              col: 2
            }
          },
          // 第4个模型数据源(一)
          {
            0: {
              row: 0,
              col: 0
            },
            1: {
              row: 0,
              col: 1
            },
            2: {
              row: 0,
              col: 2
            },
            3: {
              row: 0,
              col: 3
            }
          },
          // 第5个模型数据源(Z)
          {
            0: {
              row: 1,
              col: 1
            },
            1: {
              row: 1,
              col: 2
            },
            2: {
              row: 2,
              col: 2
            },
            3: {
              row: 2,
              col: 3
            }
          }
        ]
        // 变量
        // 当前使用的模型
        let currentModel = {}
        // 标记16宫格的位置
        let currentX = 0, currentY = 0
        // 记录所有块元素的位置
        // key=行_列 : V=块元素
        let fixedBlocks = {}
        // 定时器
        let mInterval = null
        // 最高分
        let maxScore = 0
        // 当前分数
        let score = 0
        // 入口方法
        function init() {
          createModel()
          onKeyDown()
        }
    
        init()
    
        // 根据模型使用的数据创建对应的块元素
        function createModel() {
          // 判断游戏是否结束
          if (isGameOver()) {
            gameOver()
            return
          }
          // 确定当前使用哪一个模型
          const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之间的随机数
          currentModel = MODELS[randow]
          // 重置16宫格的位置
          currentY = 0
          currentY = 0
          // 生成对应数量的块元素
          for (const key in currentModel) {
            const divEle = document.createElement('div')
            divEle.className = "activity-model"
            document.getElementById("container").appendChild(divEle)
          }
          // 定位块元素位置
          locationBlocks()
          // 模型自动下落
          autoDown()
        }
    
        // 根据数据源定位块元素的位置
        function locationBlocks() {
          // 判断一些块元素的越界行为
          checkBound()
          // 1 拿到所有的块元素
          const eles = document.getElementsByClassName("activity-model")
          for (let i = 0; i < eles.length; i++) {
            // 单个块元素
            const activityModelEle = eles[i]
            // 2 找到每个块元素对应的数据 (行、列)
            const blockModel = currentModel[i]
            // 3 根据每个块元素对应的数据来指定块元素的位置
            // 每个块元素的位置由2个值确定:
            //  1 16 宫格所在的位置
            //  2 块元素在 16 宫格中的位置 
            activityModelEle.style.top = (currentY + blockModel.row) * STEP + "px"
            activityModelEle.style.left = (currentX + blockModel.col) * STEP + "px"
          }
        }
    
        // 监听用户键盘事件
        function onKeyDown() {
          document.onkeydown = event => {
            switch (event.keyCode) {
              case 38:
                // move(0, -1)
                rotate()
                break;
              case 39:
                move(1, 0)
                break;
              case 40:
                move(0, 1)
                break;
              case 37:
                move(-1, 0)
                break;
              default:
                break;
            }
          }
        }
    
        // 移动
        function move(x, y) {
          // 控制块元素进行移动
          // const activityModelEle = document.getElementsByClassName("activity-model")[0]
          // activityModelEle.style.top = parseInt(activityModelEle.style.top || 0) + y * STEP + "px"
          // activityModelEle.style.left = parseInt(activityModelEle.style.left || 0) + x * STEP + "px"
          // 16宫格移动
          if (isMeet(currentX + x, currentY + y, currentModel)) {
            // 底部的触碰发生在移动16宫格的时候,并且此次移动是因为 Y 轴的变化而引起的
            if (y != 0) {
              // 模型之间发生触碰了
              fixedBottomModel()
            }
            return
          }
          currentX += x
          currentY += y
          // 根据16宫格的位置来重新定位块元素
          locationBlocks()
        }
        // 旋转模型
        function rotate() {
          // 算法
          // 旋转后的行 = 旋转前的列
          // 旋转后的列 = 3 - 旋转前的行
    
          // 克隆一下 currentModel 深拷贝
          const cloneCurrentModel = JSON.parse(JSON.stringify(currentModel))
    
          // 遍历模型数据源
          for (const key in cloneCurrentModel) {
            // 块元素的数据
            const blockModel = cloneCurrentModel[key]
            // 实现算法
            let temp = blockModel.row
            blockModel.row = blockModel.col
            blockModel.col = 3 - temp
          }
          // 如果旋转之后会发生触碰,那么就不需要进行旋转了
          if (isMeet(currentX, currentY, cloneCurrentModel)) {
            return
          }
          // 接受了这次旋转
          currentModel = cloneCurrentModel
          locationBlocks()
        }
    
        // 控制模型只能在容器中
        function checkBound() {
          // 定义模型可以活动的边界
          let leftBound = 0, rightBound = COL_COUNT, bottomBound = ROW_COUNT
          // 当块元素超出了边界之后,让 16 宫格后退1格
          for (const key in currentModel) {
            // 块元素的数据
            const blockModel = currentModel[key]
            // 左侧越界
            if ((blockModel.col + currentX) < 0) {
              currentX++
            }
            // 右侧越界
            if ((blockModel.col + currentX) >= rightBound) {
              currentX--
            }
            // 下侧越界
            if ((blockModel.row + currentY) >= bottomBound) {
              currentY--
              fixedBottomModel()
            }
          }
        }
    
        // 把模型固定在底部
        function fixedBottomModel() {
          // 1 改变模型的样式
          // 2 禁止模型再进行移动
          const activityModelEles = document.getElementsByClassName('activity-model')
            ;[...activityModelEles].forEach((ele, i) => {
              ele.className = "fixed-model"
              // 把该块元素放入变量中
              const blockModel = currentModel[i]
              fixedBlocks[`${currentY + blockModel.row}_${currentX + blockModel.col}`] = ele
            })
          // for (let i = activityModelEles.length - 1; i >= 0; i--) {
          //   // 拿到每个块元素
          //   const activityModelEle = activityModelEles[i]
          //   // 更改块元素的类名
          //   activityModelEle.className = "fixed-model"
          // }
          // 判断某一行是否要清理
          isRemoveLine()
          // 3 创建新的模型
          createModel()
        }
        // 判断模型之间的触碰问题
        // x, y 表示16宫格《将要》移动的位置
        // model 表示当前模型数据源《将要》完成的变化
        function isMeet(x, y, model) {
          // 所谓模型之间的触碰,在一个固定的位置已经存在一个被固定的块元素时,那么
          // 活动中的模型不可以再占用该位置
          // 判断触碰,就是在判断活动中的模型《将要移动到的位置》是否已经存在被固定
          // 的块元素
          // 返回 true 表示将要移动到的位置会发生触碰 否则返回 false
          for (const key in model) {
            const blockModel = model[key]
            // 该位置是否已经存在块元素?
            if (fixedBlocks[`${y + blockModel.row}_${x + blockModel.col}`]) {
              return true
            }
          }
          return false
        }
    
        // 判断一行是否被铺满
        function isRemoveLine() {
          // 在一行中,每一列都存在块元素,那么该行就需要被清理了
          // 遍历所有行中的所有列
          // 遍历所有行
          for (let i = 0; i < ROW_COUNT; i++) {
            // 标记符 假设当前行已经被铺满了
            let flag = true
            // 遍历当前行中的所有列
            for (let j = 0; j < COL_COUNT; j++) {
              // 如果当前行中有一列没有数据,那么就说明当前行没有被铺满
              if (!fixedBlocks[`${i}_${j}`]) {
                flag = false
                break
              }
            }
            if (flag) {
              //  该行已经被铺满了
              removeLine(i)
            }
          }
        }
    
        // 清理被铺满的这一行
        function removeLine(line) {
          // 1 删除该行中所有的块元素
          // 2 删除该行所有块元素的数据源
          // 遍历该行中的所有列
          for (let i = 0; i < COL_COUNT; i++) {
            // 1 删除该行中所有的块元素
            document.getElementById("container").removeChild(fixedBlocks[`${line}_${i}`])
            // 2 删除该行所有块元素的数据源
            fixedBlocks[`${line}_${i}`] = null
          }
          // 更新当前分数
          score += COL_COUNT
          document.getElementById("current-score").innerHTML = score
          downLine(line)
        }
    
        // 让被清理行之上的块元素下落
        function downLine(line) {
          // 1 被清理行之上的所有块元素数据源所在行数 + 1
          // 2 让块元素在容器中的位置下落
          // 3 清理之前的块元素
          // 遍历被清理行之上的所有行
          for (let i = line - 1; i >= 0; i--) {
            // 该行中的所有列
            for (let j = 0; j < COL_COUNT; j++) {
              if (!fixedBlocks[`${i}_${j}`]) continue
              // 存在数据
              // 1 被清理行之上的所有块元素数据源所在行数 + 1
              fixedBlocks[`${i + 1}_${j}`] = fixedBlocks[`${i}_${j}`]
              // 2 让块元素在容器中的位置下落
              fixedBlocks[`${i + 1}_${j}`].style.top = (i + 1) * STEP + "px"
              // 3 清理之前的块元素
              fixedBlocks[`${i}_${j}`] = null
            }
          }
        }
    
        // 让模型自动下落
        function autoDown() {
          if (mInterval) {
            clearInterval(mInterval)
          }
          mInterval = setInterval(() => {
            move(0, 1)
          }, 600)
        }
    
        // 判断游戏结束
        function isGameOver() {
          // 当第0行存在块元素的时候,表示游戏结束了
          for (let i = 0; i < COL_COUNT; i++) {
            if (fixedBlocks[`0_${i}`]) return true
          }
          return false
        }
    
        // 结束掉游戏
        function gameOver() {
          // 1 停止定时器
          if (mInterval) {
            clearInterval(mInterval)
          }
          //  重置最高分数
          maxScore = Math.max(maxScore, score)
          document.getElementById("max-score").innerHTML = maxScore
          // 2 弹出对话框
          alert("大吉大利,今晚吃鸡!")
        }
    
        // 重新开始
        function reset() {
          const container = document.getElementById("container")
          const childs = container.childNodes;
          for (let i = childs.length - 1; i >= 0; i--) {
            container.removeChild(childs[i]);
          }
          fixedBlocks = {}
          score = 0
          document.getElementById("current-score").innerHTML = score
          init()
        }
      </script>
    </body>
    
    </html>

    转载于:https://blog.csdn.net/wanghuan1020/article/details/111473709

  • 相关阅读:
    C++ String详解
    乏力的编码很累,这里有私货..
    给自己~~微语&&歌单
    2019CSP-J第二轮 B题C题
    HDU 3966 树链剖分+树状数组 模板
    HDU 2255 KM算法 二分图最大权值匹配
    HDU 4280 ISAP+BFS 最大流 模板
    HDU 6181 第k短路
    假装会python--爬取贴吧正文
    HDU 6170 dp
  • 原文地址:https://www.cnblogs.com/smileZAZ/p/14172709.html
Copyright © 2020-2023  润新知