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 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 = 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" 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;
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 = 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

最新文章

  1. language model —— basic model 语言模型之基础模型
  2. 简单的例子 关于Java内存管理的讲解
  3. 【iCore3 双核心板】例程三十五:HTTP_IAP_ARM实验——更新升级STM32
  4. SQL Server 收缩数据库
  5. ffmpeg-2.3.3 configure for android
  6. git 撤销修改以及删除文件
  7. AQS阻塞唤醒工具LockSupport
  8. ThinkPHP 框架模型
  9. JS的get和set使用示例
  10. hadoop2.5的伪分布式安装配置
  11. 前端(各种demo)三:优惠券,热区,等模块的实现(css方式)
  12. Textwrap模块
  13. 关于php得到参数数据
  14. kubernetes job的原理
  15. 【Linux命令】linux一次性解压多个.gz或者.tar.gz文件
  16. cookie session 讲解
  17. gentoo 图像方面的软件
  18. 安装mysql时包冲突解决方法
  19. 尚硅谷redis学习4-数据类型
  20. sql server复制表数据到另外一个表 的存储过程

热门文章

  1. 多任务-python实现-进程,协程,线程总结(2.1.16)
  2. 使用 vue 仿写的一个购物商城
  3. 基于Layuimini的自己封装后台模板
  4. 如何优雅地使用云原生 Prometheus 监控集群
  5. Docker实例开机启动
  6. Oracle创建表空间创建用户授权
  7. java字符统计+字符串压缩
  8. psql: could not connect to server: No such file or directory
  9. AtCoder Beginner Contest 187 F - Close Group
  10. Beta冲刺——第五天