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

  • 开始
    • 1. 创建一个宽为 `200px`,高为 `360px` 的背景容器
    • 2. 在该容器上创建一个 `20 * 20` 的块元素
    • 3. 控制该元素的移动,每次移动 `20px`
  • 构建 `L` 形状的模型
    • 1. 将容器进行分割,分割为 `18` 行,`10` 列。行高,列高均为`20`
    • 2. 以 `16宫格` 为基准,定义 `L` 形状的 `4` 个方块的位置
    • 3. 创建 `L` 型模型,根据 `16` 宫格中的数据将模型渲染到页面上
  • 控制该模型进行移动
  • 控制模型旋转
    • 规律
    • 旋转模型
  • 控制模型只在容器中移动
  • 当模型触底时,将块元素变为`灰色`固定在底部,同时生成一个新的模型
    • 声明样式类
    • 触底时固定,生成新模型
  • 判断块元素与块元素之间的碰撞,分为`左右接触`和`底部接触`
    • 记录所有块元素的位置
    • 当块元素被固定到底部的时候,将块元素存储在`fixedBlocks ` 中
    • 处理模型之间的碰撞(左右接触)
    • 处理模型之间的碰撞(底部接触)
  • 处理被铺满的行
    • 判断一行是否被铺满
    • 清理被铺满的一行
    • 让被清理行之上的块元素下落
  • 创建多种模型样式
    • 定义模型样式
    • 创建模型的时候随机选取不同的模型样式
  • 模型自动降落
  • 游戏结束
    • 判断游戏结束
    • 结束游戏
  • 扩展:计分 + 最高分 + 重新开始游戏
    • 结构 + 样式
    • 逻辑
  • 完整代码

开始

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 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;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 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;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 class="container"><!-- 块元素 --><div class="activity-model"></div></div><script>// 常量// 每次移动的距离 步长const STEP = 20init()// 入口方法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 += xcurrentY += 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.rowblockModel.row = blockModel.colblockModel.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 = 0currentY = 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 += xcurrentY += 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.rowblockModel.row = blockModel.colblockModel.col = 3 - temp}// 如果旋转之后会发生触碰,那么就不需要进行旋转了if (isMeet(currentX, currentY, cloneCurrentModel)) {return}// 接受了这次旋转currentModel = cloneCurrentModellocationBlocks()
}// 判断模型之间的触碰问题
// x, y 表示16宫格《将要》移动的位置
// model 表示当前模型数据源《将要》完成的变化
function isMeet(x, y, model) {// 所谓模型之间的触碰,在一个固定的位置已经存在一个被固定的块元素时,那么// 活动中的模型不可以再占用该位置// 判断触碰,就是在判断活动中的模型《将要移动到的位置》是否已经存在被固定// 的块元素// 返回 true 表示将要移动到的位置会发生触碰 否则返回 falsefor (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 += xcurrentY += 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 = falsebreak}}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 = falsebreak}}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 被清理行之上的所有块元素数据源所在行数 + 1fixedBlocks[`${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 = 0currentY = 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 = 0currentY = 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 = 0currentY = 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 = 0currentY = 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_COUNTdocument.getElementById("current-score").innerHTML = scoredownLine(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 = 0document.getElementById("current-score").innerHTML = scoreinit()
}

完整代码

<!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" 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 = 0currentY = 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 += xcurrentY += 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.rowblockModel.row = blockModel.colblockModel.col = 3 - temp}// 如果旋转之后会发生触碰,那么就不需要进行旋转了if (isMeet(currentX, currentY, cloneCurrentModel)) {return}// 接受了这次旋转currentModel = cloneCurrentModellocationBlocks()}// 控制模型只能在容器中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 表示将要移动到的位置会发生触碰 否则返回 falsefor (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 = falsebreak}}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_COUNTdocument.getElementById("current-score").innerHTML = scoredownLine(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 被清理行之上的所有块元素数据源所在行数 + 1fixedBlocks[`${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 = 0document.getElementById("current-score").innerHTML = scoreinit()}</script>
</body></html>

js实现简单的俄罗斯方块小游戏相关推荐

  1. php掷骰子游戏,js实现简单掷骰子小游戏

    本文实例为大家分享了js掷骰子小游戏的具体代码,供大家参考,具体内容如下 实现方法: 方法一:通过background-position.background-image.backg-repeat三个 ...

  2. html与js简单小游戏,JS实现简单贪吃蛇小游戏

    本文实例为大家分享了JS实现简单贪吃蛇游戏的具体代码,供大家参考,具体内容如下 1.使用语言 HTML+CSS+JavaScript 2.使用工具 visual studio code 3.GitHu ...

  3. HTML+js实现简单的打地鼠小游戏

    首先是内容和样式 这里是建立了3*3的方格 statu属性用于记录是否砸中了地鼠 <style type="text/css">*{margin: 0;padding: ...

  4. 回归前端学习第22天-实现俄罗斯方块小游戏5(实现单机版1——结合HTML、CSS、JS来搭建界面)

    实现单机版俄罗斯方块小游戏,搭建页面 实现静态基础页面 实现静态基础页面 HTML: <!DOCTYPE html> <html lang="en">< ...

  5. 原生JS实现一个简单的打字小游戏

    原生JS实现一个简单的打字小游戏 利用javascript制作一个简单的打字小游戏 之前写了一个贪吃蛇小游戏好像反响不错 今天我来写一个比贪吃蛇更low更简单的打字小游戏 打字小游戏原理 接下来咋们直 ...

  6. javascript俄罗斯方块小游戏

    任务要求 用javascript实现一个经典的"俄罗斯方块小游戏" 要求:能计分,有不同关卡(速度不同),有高分榜, chrome 30+以上浏览器能正常玩,主流android,i ...

  7. 基于stm32、0.96寸OLED实现的俄罗斯方块小游戏(详细源码注释)

    概述:本实验基于stm32最小系统.0.96寸OLED(68*128)和摇杆实现一个经典的俄罗斯方块小游戏.项目源码地址:点击下载. 硬件要求: 普通摇杆,两个电位器和一个开关组成,左右摇动控制一个电 ...

  8. Python编写俄罗斯方块小游戏

    俄罗斯方块是俄罗斯人发明的一款休闲类的小游戏,这款小游戏可以说是很多人童年的主打电子游戏了,本文我们使用 Python 来实现这款小游戏. 很多人学习python,不知道从何学起. 很多人学习pyth ...

  9. 分享一个蛋疼的俄罗斯方块小游戏

    分享一个蛋疼的俄罗斯方块小游戏 空间 转载请注明出处.http://www.cnblogs.com/dave_cn/ 我在Ubuntu 10.04下测试过,可以正常运行.不过界面让人蛋疼. 代码用到了 ...

最新文章

  1. 阿里云MWC 2019发布7款产品:Blink每秒可完成17亿次计算
  2. 4. SQL Server数据库状态监控 - 作业状态
  3. Mysql:一条sql是如何执行的?
  4. 由“求最大公约数“引发的思考
  5. 端计算(4)-kotlin(2)
  6. mysql5.7.14多实例安装
  7. MongoDB:GridFS删除方法删除存储桶中的所有文件
  8. 在张学友演唱会的6万观众中,AI锁定了一名逃犯
  9. JS_console对象中的一些常用方法
  10. 语言 泰克示波器程序_泰克Tektronix 任意波函数发生器AFG2000系列AFG2021
  11. uva1382 Distant Galaxy
  12. 技巧:如何从苹果Mac跟踪设备上所有电池的电量?
  13. java用那个软件编,java编译软件 编写java程序用什么软件?
  14. CorelDRAWX4的VBA插件开发(十一)弹窗界面和一键导出图片
  15. 怎么用计算机打出黑人,电脑上怎么打法文
  16. AI算法之Encoder-Decoder 和 Seq2Seq
  17. 水果店线下营销玩法有哪些,水果店前期营销方案有哪些
  18. 安卓用户必备的5款良心APP,各个都是精品,低调又实用!
  19. LCD液晶显示屏常见类型和接口
  20. 多线程 - 三种实现

热门文章

  1. 英语和计算机关系论文开题报告,计算机论文开题报告范文
  2. 现代摄影中的新朦胧主义
  3. android无线充电器支架,这款无线充电器不简单,不仅可以无线充电,还可以当手机支架...
  4. 甲骨文的历史之收购太阳公司
  5. 卢松松:SEO外链专员操作规范
  6. 复旦大学白悦辰课题组诚聘博士后、科研助理
  7. 智能优化算法:战争策略算法-附代码
  8. Python爬虫进阶——Scrapy框架原理及分布式爬虫构建
  9. 实现Windows 98与2000系统的数据共 实现Windows 98与2000系统的数据共享(转) 享(转)
  10. 快快快收藏!!Google内部Python代码风格指南(中文版)