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 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
js实现简单的俄罗斯方块小游戏的更多相关文章
- JS练习实例--编写经典小游戏俄罗斯方块
最近在学习JavaScript,想编一些实例练练手,之前编了个贪吃蛇,但是实现时没有注意使用面向对象的思想,实现起来也比较简单所以就不总结了,今天就总结下俄罗斯方块小游戏的思路和实现吧(需要下载代码也 ...
- C#俄罗斯方块小游戏程序设计与简单实现
C#俄罗斯方块小游戏程序设计与简单实现 相信90后或者80后都玩过这款小游戏,一直想干一票,琢磨一下,但又不太懂,于是网上搜集修改就有了以下效果!bug较多,多多包涵! 1.效果展示 2.实现方法 参 ...
- html+css+js实现狼吃羊小游戏
html+css+js实现狼吃羊小游戏 一.总结 一句话总结:给动的元素下标记,这里表现为将要活动的标签动态增加class,这是一种很好的思想. 1.如何实现棋子走动的时候简单精确定位? 用重构坐标系 ...
- Three.js 实现3D开放世界小游戏:阿狸的多元宇宙 🦊
声明:本文涉及图文和模型素材仅用于个人学习.研究和欣赏,请勿二次修改.非法传播.转载.出版.商用.及进行其他获利行为. 背景 2545光年之外的开普勒1028星系,有一颗色彩斑斓的宜居星球 ,星际移民 ...
- 初学JS——实现基于计时器的小游戏。
这几天一直在看网易云课堂上免费的JS课程,正好今天看到讲了计时器setInterval,第一感觉就是像C#里的TIMER.然后课程里举得例子正好通过计时器改变新生成窗口的位置, 然后就突然有了灵感!可 ...
- d3.js 制作简单的俄罗斯方块
d3.js是一个不错的可视化框架,同时对于操作dom也是十分方便的.今天我们使用d3.js配合es6的类来制作一个童年小游戏--俄罗斯方块.话不多说先上图片. 1. js tetris类 由于方法拆分 ...
- Three.js 实现3D全景侦探小游戏🕵️
背景 你是嘿嘿嘿侦探社实习侦探️,接到上级指派任务,到甄开心小镇调查市民甄不戳宝石失窃案,根据线人流浪汉老石提供的线索,小偷就躲在小镇,快把他找出来,帮甄不戳寻回失窃的宝石吧! 本文使用 Three ...
- js、jQuery实现2048小游戏
2048小游戏 一.游戏简介: 2048是一款休闲益智类的数字叠加小游戏 二. 游戏玩法: 在4*4的16宫格中,您可以选择上.下.左.右四个方向进行操作,数字会按方向移动,相邻的两个数字相同就会合 ...
- 使用 App Inventor 2 开发简单的安卓小游戏
App Inventor2 是一个简单的在线开发安卓应用程序的工具,通过此工具,我们可以很轻松地开发安卓应用. 这里介绍的是笔者自己写的一个小游戏,游戏中玩家通过左右倾斜手机控制“水库”的左右移动,收 ...
随机推荐
- IphoneX适配正确姿势
IphoneX适配正确姿势 写在前面 距离18年9月iphonex发布以来已经快两年了(所以对于iphonex机型的头部刘海(sensor housing)和底部小黑条(Home Indicator) ...
- 顶会两篇论文连发,华为云医疗AI低调中崭露头角
摘要:2020年国际医学图像计算和计算机辅助干预会议(MICCAI 2020),论文接收结果已经公布.华为云医疗AI团队和华中科技大学合作的2篇研究成果入选. 同时两篇研究成果被行业顶会收录,华为云医 ...
- Excel-VLOOKUP函数组合应用④
问题场景 查找匹配并返回多列数据,例如:将某个部门所涉及的相关列的数据从[全员数据源]中整理出来,并按照一定顺序,然后发送给各部门的负责人. 场景 从[全员数据源]中共23列数据,整理出[测试部门人员 ...
- NET 5使用HangFire定时任务
注意:1. 当Hangfire服务由Web程序来启用时,默认情况下,web应用程序中的Hangfire服务器实例在第一个用户访问您的站点之前不会启动.甚至,有一些事件会在一段时间后导致web应用程序关 ...
- C#使用时间戳
前言 参考博客 C#获取和转换时间戳: https://blog.csdn.net/weixin_39885282/article/details/79462443 获取时间戳: https://ww ...
- Hystrix监控问题
Hystrix监控问题: pom.xml: <dependency> <groupId>org.springframework.cloud</groupId> ...
- [leetcode]66Plus One
/** * Given a non-negative integer represented as a non-empty array of digits, plus one to the integ ...
- Python 字符串操作分类
应用举例: str="abc defghi jklm nopqrstuvwxyz" print(str.isspace()) 运行结果: False 1) 判断类型 方法 | 说明 ...
- niceyoo的2020年终总结-2021年Flag
碎碎念,向本命年说再见! 又到了一年一度立 Flag 的时间了,怎么样,去年的 Flag 大家实现的怎么样?还有信心立下 2021 年的 Flag 吗~ 今年我算比较背的,年初的一次小意外,直接在床上 ...
- Apache本机不同端口多站点配置:httpd-vhosts.conf(转载)
环境:Apache2.2.9,Resin-3.1.6,Win Server 2003 1.解压Resin至任意目录,我的是D:; 2. 安装Apache,具体操作下一步.下一步即可,其中要配置的地方是 ...