2020年12月22日星期二

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;  width: 200px;  height: 360px;  background-color: #000; } </style></head><body> <!-- 背景容器 --> <div ></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;  width: 200px;  height: 360px;  background-color: #000; } .activity-model {  width: 20px;  height: 20px;  background-color: cadetblue;  border: 1px solid #eeeeee;  box-sizing: border-box;  position: absolute; } </style></head><body> <!-- 背景容器 --> <div > <!-- 块元素 --> <div ></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;  width: 200px;  height: 360px;  background-color: #000; } .activity-model {  width: 20px;  height: 20px;  background-color: cadetblue;  border: 1px solid #eeeeee;  box-sizing: border-box;  position: absolute; } </style></head><body> <!-- 背景容器 --> <div > <!-- 块元素 --> <div ></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 { width: 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" > <!-- 块元素 --> <!-- <div ></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;  width: 200px;  height: 360px;  background-color: #000; } .activity-model {  width: 20px;  height: 20px;  background-color: cadetblue;  border: 1px solid #eeeeee;  box-sizing: border-box;  position: absolute; } .fixed-model {  width: 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" > <!-- 块元素 --> <!-- <div ></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









原文转载:http://www.shaoqun.com/a/502616.html

跨境电商:https://www.ikjzd.com/

terapeak:https://www.ikjzd.com/w/556

乐宝:https://www.ikjzd.com/w/2200


js实现简单的俄罗斯方块小游戏开始1.创建一个宽为200px,高为360px的背景容器<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=devi
extra:extra
zozotown:zozotown
马来西亚热浪岛旅游攻略:马来西亚热浪岛旅游攻略
清远漂流,哪个是最好的?哪个是最有名的?:清远漂流,哪个是最好的?哪个是最有名的?
在墨西哥看夺目绚彩的怪兽 :在墨西哥看夺目绚彩的怪兽

没有评论:

发表评论