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 是一个简单的在线开发安卓应用程序的工具,通过此工具,我们可以很轻松地开发安卓应用. 这里介绍的是笔者自己写的一个小游戏,游戏中玩家通过左右倾斜手机控制“水库”的左右移动,收 ...
随机推荐
- Animate..css的一些属性使用
使用基本的代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <t ...
- vue API 知识点(3) --- 实例 总结
一.实例 property 1.vm.$data Vue 实例观察的数据对象,Vue 实例代理了对其 data 对象 property 的的访问 2.vm.$props 当前组件接收到的 props ...
- Python利用openpyxl带格式统计数据(1)- 处理excel数据
统计数据的随笔写了两篇了,再来一篇,这是第三篇,前面第一篇是用xlwt写excel数据,第二篇是用xlwt写mysql数据.先贴要处理的数据截图: 再贴最终要求的统计格式截图: 第三贴代码: 1 '' ...
- PHP简单的计算位数的函数
一个简单的PHP计算位数的函数: 1 <?php 2 //一个简单的计算字符串有长度的函数 3 #开始定义函数 4 function count_digit($number){ 5 $count ...
- JavaWeb基础总结:Servlet专题
最近工作中有部分整改老接口的任务,大部分与Spring的拦截器,Tomcat相关,改到一些底层的代码发现,对基础J2EE的知识有些遗忘,需要频繁查阅,索性从头系统的整理一下Servlet和Filter ...
- 【mybatis-plus】CRUD必备良药,mybatis的好搭档
做开发,免不了对数据进行增删改查,那么mybatis-plus我觉得很适合我这个java新手,简单好用. 官网在这 一.什么是mybatis-plus MyBatis-Plus(简称 MP),是一个M ...
- .NETCore使用EntityFrameworkCore连接数据库生成实体
EF Core 通过数据库提供程序插件模型与 SQL Server/SQL Azure.SQLite.Azure Cosmos DB.MySQL.PostgreSQL 和更多数据库配合使用. 使用EF ...
- 各开源协议BSD、Apache Licence 2.0、GPL
以下是上述协议的简单介绍:BSD开源协议BSD开源协议是一个给于使用者很大自由的协议.基本上使用者可以"为所欲为",可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有 ...
- Spring Cloud Hystrix原理篇(十一)
一.Hystrix处理流程 Hystrix流程图如下: Hystrix整个工作流如下: 构造一个 HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在 ...
- github与svn的区别
github与svn都属于版本控件系统,但是两者不同于,github是分布式的,svn不是分布的是属于集中式的. 1) 最核心的区别Git是分布式的,而Svn不是分布的.能理解这点,上手会很容 ...