js實作簡單的俄羅斯方塊小游戲
開始
1. 創建一個寬為 200px,高為 360px 的背景容器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>俄羅斯方塊</title>
<style>
.container {
position: relative;
width: 200px;
height: 360px;
background-color: #000;
}
</style>
</head>
<body>
<!-- 背景容器 -->
<div ></div>
</body>
</html>
2. 在該容器上創建一個 20 * 20 的塊元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>俄羅斯方塊</title>
<style>
.container {
position: relative;
width: 200px;
height: 360px;
background-color: #000;
}
.activity-model {
width: 20px;
height: 20px;
background-color: cadetblue;
border: 1px solid #eeeeee;
box-sizing: border-box;
position: absolute;
}
</style>
</head>
<body>
<!-- 背景容器 -->
<div >
<!-- 塊元素 -->
<div ></div>
</div>
</body>
</html>
3. 控制該元素的移動,每次移動 20px
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>俄羅斯方塊</title>
<style>
.container {
position: relative;
width: 200px;
height: 360px;
background-color: #000;
}
.activity-model {
width: 20px;
height: 20px;
background-color: cadetblue;
border: 1px solid #eeeeee;
box-sizing: border-box;
position: absolute;
}
</style>
</head>
<body>
<!-- 背景容器 -->
<div >
<!-- 塊元素 -->
<div ></div>
</div>
<script>
// 常量
// 每次移動的距離 步長
const STEP = 20
init()
// 入口方法
function init() {
onKeyDown()
}
// 監聽用戶的鍵盤事件
function onKeyDown() {
document.onkeydown = event => {
switch (event.keyCode) {
case 38: // 上
move(0, -1)
break;
case 39: // 右
move(1, 0)
break;
case 40: // 下
move(0, 1)
break;
case 37: // 左
move(-1, 0)
break;
default:
break;
}
}
}
// 移動
function move(x, y) {
// 控制塊元素進行移動
const activityModelEle = document.getElementsByClassName("activity-model")[0]
activityModelEle.style.top = parseInt(activityModelEle.style.top || 0) + y * STEP + "px"
activityModelEle.style.left = parseInt(activityModelEle.style.left || 0) + x * STEP + "px"
}
</script>
</body>
</html>
構建 L 形狀的模型
1. 將容器進行分割,分割為 18 行,10 列,行高,列高均為20

// 常量 // 每次移動的距離 步長 const STEP = 20 // 分割容器 // 18行 10列 const ROW_COUNT = 18, COL_COUNT = 10
2. 以 16宮格 為基準,定義 L 形狀的 4 個方塊的位置
// 分割容器
// 18行 10列
const ROW_COUNT = 18, COL_COUNT = 10
// 創建每個模型的資料源
const MODELS = [
// 第1個模型資料源(L型)
{
0: {
row: 2,
col: 0
},
1: {
row: 2,
col: 1
},
2: {
row: 2,
col: 2
},
3: {
row: 1,
col: 2
}
}]
3. 創建 L 型模型,根據 16 宮格中的資料將模型渲染到頁面上
// 分割容器
// 18行 10列
const ROW_COUNT = 18, COL_COUNT = 10
// 創建每個模型的資料源
const MODELS = [
// 第1個模型資料源(L型)
{
0: {
row: 2,
col: 0
},
1: {
row: 2,
col: 1
},
2: {
row: 2,
col: 2
},
3: {
row: 1,
col: 2
}
}]
// 變數
// 當前使用的模型
let currentModel = {}
init()
// 入口方法
function init() {
createModel()
onKeyDown()
}
// 根據模型使用的資料創建對應的塊元素
function createModel() {
// 確定當前使用哪一個模型
currentModel = MODELS[0]
// 生成對應數量的塊元素
for (const key in currentModel) {
const divEle = document.createElement('div')
divEle.className = "activity-model"
document.getElementById("container").appendChild(divEle)
}
// 定位塊元素位置
locationBlocks()
}
// 根據資料源定位塊元素的位置
function locationBlocks() {
// 1 拿到所有的塊元素
const eles = document.getElementsByClassName("activity-model")
for (let i = 0; i < eles.length; i++) {
// 單個塊元素
const activityModelEle = eles[i]
// 2 找到每個塊元素對應的資料 (行、列)
const blockModel = currentModel[i]
// 3 根據每個塊元素對應的資料來指定塊元素的位置
activityModelEle.style.top = blockModel.row * STEP + "px"
activityModelEle.style.left = blockModel.col * STEP + "px"
}
}
控制該模型進行移動
-
本質是控制
16 宮格 進行移動
// 根據資料源定位塊元素的位置
function locationBlocks() {
// 1 拿到所有的塊元素
const eles = document.getElementsByClassName("activity-model")
for (let i = 0; i < eles.length; i++) {
// 單個塊元素
const activityModelEle = eles[i]
// 2 找到每個塊元素對應的資料 (行、列)
const blockModel = currentModel[i]
// 3 根據每個塊元素對應的資料來指定塊元素的位置
// 每個塊元素的位置由2個值確定:
// a. 16 宮格所在的位置
// b. 塊元素在 16 宮格中的位置
activityModelEle.style.top = (currentY + blockModel.row) * STEP + "px"
activityModelEle.style.left = (currentX + blockModel.col) * STEP + "px"
}
}
// 移動
function move(x, y) {
// 控制16宮格元素進行移動
currentX += x
currentY += y
// 根據16宮格的位置來重新定位塊元素
locationBlocks()
}
控制模型旋轉

規律
-
以 16宮格 的中心點為基準進行旋轉
-
觀察上圖中旋轉后每個塊元素發生的位置的變化
-
以第1,2個L模型為例,可以觀察到:…
- 塊元素1的坐標(列, 行)變化:(0, 2) -> (1, 0)
- 塊元素2的坐標(列, 行)變化:(1, 2) -> (1, 1)
- 塊元素3的坐標(列, 行)變化:(2, 2) -> (1, 2)
- 塊元素4的坐標(列, 行)變化:(2, 1) -> (2, 2)
-
其基本變化規律是
移動后的行 = 移動前的列移動后的列 = 3 - 移動前的行
旋轉模型
// 監聽用戶的鍵盤事件
function onKeyDown() {
document.onkeydown = event => {
switch (event.keyCode) {
case 38: // 上
// move(0, -1)
rotate()
break;
case 39: // 右
move(1, 0)
break;
case 40: // 下
move(0, 1)
break;
case 37: // 左
move(-1, 0)
break;
default:
break;
}
}
}
// 旋轉模型
function rotate() {
// 演算法
// 旋轉后的行 = 旋轉前的列
// 旋轉后的列 = 3 - 旋轉前的行
// 遍歷模型資料源
for (const key in currentModel) {
// 塊元素的資料
const blockModel = currentModel[key]
// 實作演算法
let temp = blockModel.row
blockModel.row = blockModel.col
blockModel.col = 3 - temp
}
locationBlocks()
}
控制模型只在容器中移動
// 根據資料源定位塊元素的位置
function locationBlocks() {
// 判斷一下塊元素的越界行為
checkBound()
// 1 拿到所有的塊元素
const eles = document.getElementsByClassName("activity-model")
for (let i = 0; i < eles.length; i++) {
// 單個塊元素
const activityModelEle = eles[i]
// 2 找到每個塊元素對應的資料 (行、列)
const blockModel = currentModel[i]
// 3 根據每個塊元素對應的資料來指定塊元素的位置
// 每個塊元素的位置由2個值確定:
// a. 16 宮格所在的位置
// b. 塊元素在 16 宮格中的位置
activityModelEle.style.top = (currentY + blockModel.row) * STEP + "px"
activityModelEle.style.left = (currentX + blockModel.col) * STEP + "px"
}
}
// 控制模型只能在容器中
function checkBound() {
// 定義模型可以活動的邊界
let leftBound = 0, rightBound = COL_COUNT, bottomBound = ROW_COUNT
// 當塊元素超出了邊界之后,讓 16 宮格后退1格
for (const key in currentModel) {
// 塊元素的資料
const blockModel = currentModel[key]
// 左側越界
if ((blockModel.col + currentX) < 0) {
currentX++
}
// 右側越界
if ((blockModel.col + currentX) >= rightBound) {
currentX--
}
// 底部越界
if ((blockModel.row + currentY) >= bottomBound) {
currentY--
}
}
}
當模型觸底時,將塊元素變為灰色固定在底部,同時生成一個新的模型
宣告樣式類
.fixed-model {
width: 20px;
height: 20px;
background-color: #fefefe;
border: 1px solid #333333;
box-sizing: border-box;
position: absolute;
}
觸底時固定,生成新模型
-
需要注意的是:當模型觸底被固定后,我們需要重新再生成一個新的模型,再生成新模型的時候,需要重置 16宮格 的位置,否則新創建的模型的位置會出現在底部,并將上一模型覆寫掉
// 根據模型使用的資料創建對應的塊元素
function createModel() {
// 確定當前使用哪一個模型
currentModel = MODELS[0]
// 重置16宮格的位置
currentY = 0
currentY = 0
// 生成對應數量的塊元素
for (const key in currentModel) {
const divEle = document.createElement('div')
divEle.className = "activity-model"
document.getElementById("container").appendChild(divEle)
}
// 定位塊元素位置
locationBlocks()
}
// 控制模型只能在容器中
function checkBound() {
// 定義模型可以活動的邊界
let leftBound = 0, rightBound = COL_COUNT, bottomBound = ROW_COUNT
// 當塊元素超出了邊界之后,讓 16 宮格后退1格
for (const key in currentModel) {
// 塊元素的資料
const blockModel = currentModel[key]
// 左側越界
if ((blockModel.col + currentX) < 0) {
currentX++
}
// 右側越界
if ((blockModel.col + currentX) >= rightBound) {
currentX--
}
// 底部越界
if ((blockModel.row + currentY) >= bottomBound) {
currentY--
fixedBottomModel() // 把模型固定在底部
}
}
}
// 把模型固定在底部
function fixedBottomModel() {
// 1 改變模型的樣式
// 2 禁止模型再進行移動
const activityModelEles = document.getElementsByClassName('activity-model')
;[...activityModelEles].forEach((ele, i) => {
// 更改塊元素類名
ele.className = "fixed-model"
// 把該塊元素放入變數中
const blockModel = currentModel[i]
fixedBlocks[`${currentY + blockModel.row}_${currentX + blockModel.col}`] = ele
})
// 3 創建新的模型
createModel()
}
判斷塊元素與塊元素之間的碰撞,分為左右接觸和底部接觸
記錄所有塊元素的位置
// 記錄所有塊元素的位置
// key=行_列 : V=塊元素
const fixedBlocks = {}
當塊元素被固定到底部的時候,將塊元素存盤在fixedBlocks 中
// 把模型固定在底部
function fixedBottomModel() {
// 1 改變模型的樣式
// 2 禁止模型再進行移動
const activityModelEles = document.getElementsByClassName('activity-model')
;[...activityModelEles].forEach((ele, i) => {
// 更改塊元素類名
ele.className = "fixed-model"
// 把該塊元素放入變數中
const blockModel = currentModel[i]
fixedBlocks[`${currentY + blockModel.row}_${currentX + blockModel.col}`] = ele
})
// 3 創建新的模型
createModel()
}
處理模型之間的碰撞(左右接觸)
// 移動
function move(x, y) {
// 16宮格移動
if (isMeet(currentX + x, currentY + y, currentModel)) {
return
}
currentX += x
currentY += y
// 根據16宮格的位置來重新定位塊元素
locationBlocks()
}
// 旋轉模型
function rotate() {
// 演算法
// 旋轉后的行 = 旋轉前的列
// 旋轉后的列 = 3 - 旋轉前的行
// 克隆一下 currentModel 深拷貝
const cloneCurrentModel = JSON.parse(JSON.stringify(currentModel))
// 遍歷模型資料源
for (const key in cloneCurrentModel) {
// 塊元素的資料
const blockModel = cloneCurrentModel[key]
// 實作演算法
let temp = blockModel.row
blockModel.row = blockModel.col
blockModel.col = 3 - temp
}
// 如果旋轉之后會發生觸碰,那么就不需要進行旋轉了
if (isMeet(currentX, currentY, cloneCurrentModel)) {
return
}
// 接受了這次旋轉
currentModel = cloneCurrentModel
locationBlocks()
}
// 判斷模型之間的觸碰問題
// x, y 表示16宮格《將要》移動的位置
// model 表示當前模型資料源《將要》完成的變化
function isMeet(x, y, model) {
// 所謂模型之間的觸碰,在一個固定的位置已經存在一個被固定的塊元素時,那么
// 活動中的模型不可以再占用該位置
// 判斷觸碰,就是在判斷活動中的模型《將要移動到的位置》是否已經存在被固定
// 的塊元素
// 回傳 true 表示將要移動到的位置會發生觸碰 否則回傳 false
for (const key in model) {
const blockModel = model[key]
// 該位置是否已經存在塊元素?
if (fixedBlocks[`${y + blockModel.row}_${x + blockModel.col}`]) {
return true
}
}
return false
}
處理模型之間的碰撞(底部接觸)
// 移動
function move(x, y) {
if (isMeet(currentX + x, currentY + y, currentModel)) {
// 底部的觸碰發生在移動16宮格的時候,并且此次移動是因為 Y 軸的變化而引起的
if (y != 0) {
// 模型之間發生觸碰了
fixedBottomModel()
}
return
}
// 控制16宮格元素進行移動
currentX += x
currentY += y
// 根據16宮格的位置來重新定位塊元素
locationBlocks()
}
處理被鋪滿的行
判斷一行是否被鋪滿
// 把模型固定在底部
function fixedBottomModel() {
// 1 改變模型的樣式
// 2 禁止模型再進行移動
const activityModelEles = document.getElementsByClassName('activity-model')
;[...activityModelEles].forEach((ele, i) => {
ele.className = "fixed-model"
// 把該塊元素放入變數中
const blockModel = currentModel[i]
fixedBlocks[`${currentY + blockModel.row}_${currentX + blockModel.col}`] = ele
})
// 判斷某一行是否要清理
isRemoveLine()
// 3 創建新的模型
createModel()
}
// 判斷一行是否被鋪滿
function isRemoveLine() {
// 在一行中,每一列都存在塊元素,那么該行就需要被清理了
// 遍歷所有行中的所有列
// 遍歷所有行
for (let i = 0; i < ROW_COUNT; i++) {
// 標記符 假設當前行已經被鋪滿了
let flag = true
// 遍歷當前行中的所有列
for (let j = 0; j < COL_COUNT; j++) {
// 如果當前行中有一列沒有資料,那么就說明當前行沒有被鋪滿
if (!fixedBlocks[`${i}_${j}`]) {
flag = false
break
}
}
if (flag) {
// 該行已經被鋪滿了
console.log("該行已經被鋪滿了")
}
}
}
清理被鋪滿的一行
function isRemoveLine() {
// 在一行中,每一列都存在塊元素,那么該行就需要被清理了
// 遍歷所有行中的所有列
// 遍歷所有行
for (let i = 0; i < ROW_COUNT; i++) {
// 標記符 假設當前行已經被鋪滿了
let flag = true
// 遍歷當前行中的所有列
for (let j = 0; j < COL_COUNT; j++) {
// 如果當前行中有一列沒有資料,那么就說明當前行沒有被鋪滿
if (!fixedBlocks[`${i}_${j}`]) {
flag = false
break
}
}
if (flag) {
// 該行已經被鋪滿了
removeLine(i)
}
}
}
// 清理被鋪滿的這一行
function removeLine(line) {
// 1 洗掉該行中所有的塊元素
// 2 洗掉該行所有塊元素的資料源
// 遍歷該行中的所有列
for (let i = 0; i < COL_COUNT; i++) {
// 1 洗掉該行中所有的塊元素
document.getElementById("container").removeChild(fixedBlocks[`${line}_${i}`])
// 2 洗掉該行所有塊元素的資料源
fixedBlocks[`${line}_${i}`] = null
}
}
讓被清理行之上的塊元素下落
// 清理被鋪滿的這一行
function removeLine(line) {
// 1 洗掉該行中所有的塊元素
// 2 洗掉該行所有塊元素的資料源
// 遍歷該行中的所有列
for (let i = 0; i < COL_COUNT; i++) {
// 1 洗掉該行中所有的塊元素
document.getElementById("container").removeChild(fixedBlocks[`${line}_${i}`])
// 2 洗掉該行所有塊元素的資料源
fixedBlocks[`${line}_${i}`] = null
}
downLine(line)
}
// 讓被清理行之上的塊元素下落
function downLine(line) {
// 1 被清理行之上的所有塊元素資料源所在行數 + 1
// 2 讓塊元素在容器中的位置下落
// 3 清理之前的塊元素
// 遍歷被清理行之上的所有行
for (let i = line - 1; i >= 0; i--) {
// 該行中的所有列
for (let j = 0; j < COL_COUNT; j++) {
if (!fixedBlocks[`${i}_${j}`]) continue
// 存在資料
// 1 被清理行之上的所有塊元素資料源所在行數 + 1
fixedBlocks[`${i + 1}_${j}`] = fixedBlocks[`${i}_${j}`]
// 2 讓塊元素在容器中的位置下落
fixedBlocks[`${i + 1}_${j}`].style.top = (i + 1) * STEP + "px"
// 3 清理之前的塊元素
fixedBlocks[`${i}_${j}`] = null
}
}
}
創建多種模型樣式
定義模型樣式
// 創建每個模型的資料源
const MODELS = [
// 第1個模型資料源(L型)
{
0: {
row: 2,
col: 0
},
1: {
row: 2,
col: 1
},
2: {
row: 2,
col: 2
},
3: {
row: 1,
col: 2
}
},
// 第2個模型資料源(凸)
{
0: {
row: 1,
col: 1
},
1: {
row: 0,
col: 0
},
2: {
row: 1,
col: 0
},
3: {
row: 2,
col: 0
}
},
// 第3個模型資料源(田)
{
0: {
row: 1,
col: 1
},
1: {
row: 2,
col: 1
},
2: {
row: 1,
col: 2
},
3: {
row: 2,
col: 2
}
},
// 第4個模型資料源(一)
{
0: {
row: 0,
col: 0
},
1: {
row: 0,
col: 1
},
2: {
row: 0,
col: 2
},
3: {
row: 0,
col: 3
}
},
// 第5個模型資料源(Z)
{
0: {
row: 1,
col: 1
},
1: {
row: 1,
col: 2
},
2: {
row: 2,
col: 2
},
3: {
row: 2,
col: 3
}
}
]
創建模型的時候隨機選取不同的模型樣式
// 根據模型使用的資料創建對應的塊元素
function createModel() {
// 確定當前使用哪一個模型
const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之間的亂數
currentModel = MODELS[randow]
// 重置16宮格的位置
currentY = 0
currentY = 0
// 生成對應數量的塊元素
for (const key in currentModel) {
const divEle = document.createElement('div')
divEle.className = "activity-model"
document.getElementById("container").appendChild(divEle)
}
// 定位塊元素位置
locationBlocks()
}
模型自動降落
// 定時器
let mInterval = null
// 根據模型使用的資料創建對應的塊元素
function createModel() {
// 確定當前使用哪一個模型
// 確定當前使用哪一個模型
const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之間的亂數
currentModel = MODELS[randow]
// 重置16宮格的位置
currentY = 0
currentY = 0
// 生成對應數量的塊元素
for (const key in currentModel) {
const divEle = document.createElement('div')
divEle.className = "activity-model"
document.getElementById("container").appendChild(divEle)
}
// 定位塊元素位置
locationBlocks()
// 模型自動下落
autoDown()
}
// 讓模型自動下落
function autoDown() {
if (mInterval) {
clearInterval(mInterval)
}
mInterval = setInterval(() => {
move(0, 1)
}, 600)
}
游戲結束
判斷游戲結束
// 根據模型使用的資料創建對應的塊元素
function createModel() {
// 判斷游戲是否結束
if (isGameOver()) {
console.log("游戲結束!")
return
}
// 確定當前使用哪一個模型
const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之間的亂數
currentModel = MODELS[randow]
// 重置16宮格的位置
currentY = 0
currentY = 0
// 生成對應數量的塊元素
for (const key in currentModel) {
const divEle = document.createElement('div')
divEle.className = "activity-model"
document.getElementById("container").appendChild(divEle)
}
// 定位塊元素位置
locationBlocks()
// 模型自動下落
autoDown()
}
// 判斷游戲結束
function isGameOver() {
// 當第0行存在塊元素的時候,表示游戲結束了
for (let i = 0; i < COL_COUNT; i++) {
if (fixedBlocks[`0_${i}`]) return true
}
return false
}
結束游戲
// 根據模型使用的資料創建對應的塊元素
function createModel() {
// 判斷游戲是否結束
if (isGameOver()) {
gameOver() // 結束游戲
return
}
// 確定當前使用哪一個模型
const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之間的亂數
currentModel = MODELS[randow]
// 重置16宮格的位置
currentY = 0
currentY = 0
// 生成對應數量的塊元素
for (const key in currentModel) {
const divEle = document.createElement('div')
divEle.className = "activity-model"
document.getElementById("container").appendChild(divEle)
}
// 定位塊元素位置
locationBlocks()
// 模型自動下落
autoDown()
}
// 結束掉游戲
function gameOver() {
// 1 停止定時器
if (mInterval) {
clearInterval(mInterval)
}
// 2 彈出對話框
alert("大吉大利,今晚吃雞!")
}
擴展:計分 + 最高分 + 重新開始游戲
結構 + 樣式
body {
display: flex;
}
#scores {
margin-left: 20px;
}
<!-- 背景容器 --> <div id="container" > <!-- 塊元素 --> <!-- <div ></div> --> </div> <div id="scores"> <p>最高分:<span id="max-score">0</span></p> <p>分數:<span id="current-score">0</span></p> <button onclick="reset()">重新開始</button> </div>
邏輯
// 最高分
let maxScore = 0
// 當前分數
let score = 0
// 清理被鋪滿的這一行
function removeLine(line) {
// 1 洗掉該行中所有的塊元素
// 2 洗掉該行所有塊元素的資料源
// 遍歷該行中的所有列
for (let i = 0; i < COL_COUNT; i++) {
// 1 洗掉該行中所有的塊元素
document.getElementById("container").removeChild(fixedBlocks[`${line}_${i}`])
// 2 洗掉該行所有塊元素的資料源
fixedBlocks[`${line}_${i}`] = null
}
// 更新當前分數
score += COL_COUNT
document.getElementById("current-score").innerHTML = score
downLine(line)
}
// 結束掉游戲
function gameOver() {
// 1 停止定時器
if (mInterval) {
clearInterval(mInterval)
}
// 重置最高分數
maxScore = Math.max(maxScore, score)
document.getElementById("max-score").innerHTML = maxScore
// 2 彈出對話框
alert("大吉大利,今晚吃雞!")
}
// 重新開始
function reset() {
const container = document.getElementById("container")
const childs = container.childNodes;
for (let i = childs.length - 1; i >= 0; i--) {
container.removeChild(childs[i]);
}
fixedBlocks = {}
score = 0
document.getElementById("current-score").innerHTML = score
init()
}
完整代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>俄羅斯方塊</title>
<style>
body {
display: flex;
}
.container {
position: relative;
width: 200px;
height: 360px;
background-color: #000;
}
.activity-model {
width: 20px;
height: 20px;
background-color: cadetblue;
border: 1px solid #eeeeee;
box-sizing: border-box;
position: absolute;
}
.fixed-model {
width: 20px;
height: 20px;
background-color: #fefefe;
border: 1px solid #333333;
box-sizing: border-box;
position: absolute;
}
#scores {
margin-left: 20px;
}
</style>
</head>
<body>
<!-- 背景容器 -->
<div id="container" >
<!-- 塊元素 -->
<!-- <div ></div> -->
</div>
<div id="scores">
<p>最高分:<span id="max-score">0</span></p>
<p>分數:<span id="current-score">0</span></p>
<button onclick="reset()">重新開始</button>
</div>
<script>
// 常量
// 每次移動的距離 步長
const STEP = 20
// 分割容器
// 18行 10列
const ROW_COUNT = 18, COL_COUNT = 10
// 創建每個模型的資料源
const MODELS = [
// 第1個模型資料源(L型)
{
0: {
row: 2,
col: 0
},
1: {
row: 2,
col: 1
},
2: {
row: 2,
col: 2
},
3: {
row: 1,
col: 2
}
},
// 第2個模型資料源(凸)
{
0: {
row: 1,
col: 1
},
1: {
row: 0,
col: 0
},
2: {
row: 1,
col: 0
},
3: {
row: 2,
col: 0
}
},
// 第3個模型資料源(田)
{
0: {
row: 1,
col: 1
},
1: {
row: 2,
col: 1
},
2: {
row: 1,
col: 2
},
3: {
row: 2,
col: 2
}
},
// 第4個模型資料源(一)
{
0: {
row: 0,
col: 0
},
1: {
row: 0,
col: 1
},
2: {
row: 0,
col: 2
},
3: {
row: 0,
col: 3
}
},
// 第5個模型資料源(Z)
{
0: {
row: 1,
col: 1
},
1: {
row: 1,
col: 2
},
2: {
row: 2,
col: 2
},
3: {
row: 2,
col: 3
}
}
]
// 變數
// 當前使用的模型
let currentModel = {}
// 標記16宮格的位置
let currentX = 0, currentY = 0
// 記錄所有塊元素的位置
// key=行_列 : V=塊元素
let fixedBlocks = {}
// 定時器
let mInterval = null
// 最高分
let maxScore = 0
// 當前分數
let score = 0
// 入口方法
function init() {
createModel()
onKeyDown()
}
init()
// 根據模型使用的資料創建對應的塊元素
function createModel() {
// 判斷游戲是否結束
if (isGameOver()) {
gameOver()
return
}
// 確定當前使用哪一個模型
const randow = Math.floor(Math.random() * MODELS.length) // 取0~4之間的亂數
currentModel = MODELS[randow]
// 重置16宮格的位置
currentY = 0
currentY = 0
// 生成對應數量的塊元素
for (const key in currentModel) {
const divEle = document.createElement('div')
divEle.className = "activity-model"
document.getElementById("container").appendChild(divEle)
}
// 定位塊元素位置
locationBlocks()
// 模型自動下落
autoDown()
}
// 根據資料源定位塊元素的位置
function locationBlocks() {
// 判斷一些塊元素的越界行為
checkBound()
// 1 拿到所有的塊元素
const eles = document.getElementsByClassName("activity-model")
for (let i = 0; i < eles.length; i++) {
// 單個塊元素
const activityModelEle = eles[i]
// 2 找到每個塊元素對應的資料 (行、列)
const blockModel = currentModel[i]
// 3 根據每個塊元素對應的資料來指定塊元素的位置
// 每個塊元素的位置由2個值確定:
// 1 16 宮格所在的位置
// 2 塊元素在 16 宮格中的位置
activityModelEle.style.top = (currentY + blockModel.row) * STEP + "px"
activityModelEle.style.left = (currentX + blockModel.col) * STEP + "px"
}
}
// 監聽用戶鍵盤事件
function onKeyDown() {
document.onkeydown = event => {
switch (event.keyCode) {
case 38:
// move(0, -1)
rotate()
break;
case 39:
move(1, 0)
break;
case 40:
move(0, 1)
break;
case 37:
move(-1, 0)
break;
default:
break;
}
}
}
// 移動
function move(x, y) {
// 控制塊元素進行移動
// const activityModelEle = document.getElementsByClassName("activity-model")[0]
// activityModelEle.style.top = parseInt(activityModelEle.style.top || 0) + y * STEP + "px"
// activityModelEle.style.left = parseInt(activityModelEle.style.left || 0) + x * STEP + "px"
// 16宮格移動
if (isMeet(currentX + x, currentY + y, currentModel)) {
// 底部的觸碰發生在移動16宮格的時候,并且此次移動是因為 Y 軸的變化而引起的
if (y != 0) {
// 模型之間發生觸碰了
fixedBottomModel()
}
return
}
currentX += x
currentY += y
// 根據16宮格的位置來重新定位塊元素
locationBlocks()
}
// 旋轉模型
function rotate() {
// 演算法
// 旋轉后的行 = 旋轉前的列
// 旋轉后的列 = 3 - 旋轉前的行
// 克隆一下 currentModel 深拷貝
const cloneCurrentModel = JSON.parse(JSON.stringify(currentModel))
// 遍歷模型資料源
for (const key in cloneCurrentModel) {
// 塊元素的資料
const blockModel = cloneCurrentModel[key]
// 實作演算法
let temp = blockModel.row
blockModel.row = blockModel.col
blockModel.col = 3 - temp
}
// 如果旋轉之后會發生觸碰,那么就不需要進行旋轉了
if (isMeet(currentX, currentY, cloneCurrentModel)) {
return
}
// 接受了這次旋轉
currentModel = cloneCurrentModel
locationBlocks()
}
// 控制模型只能在容器中
function checkBound() {
// 定義模型可以活動的邊界
let leftBound = 0, rightBound = COL_COUNT, bottomBound = ROW_COUNT
// 當塊元素超出了邊界之后,讓 16 宮格后退1格
for (const key in currentModel) {
// 塊元素的資料
const blockModel = currentModel[key]
// 左側越界
if ((blockModel.col + currentX) < 0) {
currentX++
}
// 右側越界
if ((blockModel.col + currentX) >= rightBound) {
currentX--
}
// 下側越界
if ((blockModel.row + currentY) >= bottomBound) {
currentY--
fixedBottomModel()
}
}
}
// 把模型固定在底部
function fixedBottomModel() {
// 1 改變模型的樣式
// 2 禁止模型再進行移動
const activityModelEles = document.getElementsByClassName('activity-model')
;[...activityModelEles].forEach((ele, i) => {
ele.className = "fixed-model"
// 把該塊元素放入變數中
const blockModel = currentModel[i]
fixedBlocks[`${currentY + blockModel.row}_${currentX + blockModel.col}`] = ele
})
// for (let i = activityModelEles.length - 1; i >= 0; i--) {
// // 拿到每個塊元素
// const activityModelEle = activityModelEles[i]
// // 更改塊元素的類名
// activityModelEle.className = "fixed-model"
// }
// 判斷某一行是否要清理
isRemoveLine()
// 3 創建新的模型
createModel()
}
// 判斷模型之間的觸碰問題
// x, y 表示16宮格《將要》移動的位置
// model 表示當前模型資料源《將要》完成的變化
function isMeet(x, y, model) {
// 所謂模型之間的觸碰,在一個固定的位置已經存在一個被固定的塊元素時,那么
// 活動中的模型不可以再占用該位置
// 判斷觸碰,就是在判斷活動中的模型《將要移動到的位置》是否已經存在被固定
// 的塊元素
// 回傳 true 表示將要移動到的位置會發生觸碰 否則回傳 false
for (const key in model) {
const blockModel = model[key]
// 該位置是否已經存在塊元素?
if (fixedBlocks[`${y + blockModel.row}_${x + blockModel.col}`]) {
return true
}
}
return false
}
// 判斷一行是否被鋪滿
function isRemoveLine() {
// 在一行中,每一列都存在塊元素,那么該行就需要被清理了
// 遍歷所有行中的所有列
// 遍歷所有行
for (let i = 0; i < ROW_COUNT; i++) {
// 標記符 假設當前行已經被鋪滿了
let flag = true
// 遍歷當前行中的所有列
for (let j = 0; j < COL_COUNT; j++) {
// 如果當前行中有一列沒有資料,那么就說明當前行沒有被鋪滿
if (!fixedBlocks[`${i}_${j}`]) {
flag = false
break
}
}
if (flag) {
// 該行已經被鋪滿了
removeLine(i)
}
}
}
// 清理被鋪滿的這一行
function removeLine(line) {
// 1 洗掉該行中所有的塊元素
// 2 洗掉該行所有塊元素的資料源
// 遍歷該行中的所有列
for (let i = 0; i < COL_COUNT; i++) {
// 1 洗掉該行中所有的塊元素
document.getElementById("container").removeChild(fixedBlocks[`${line}_${i}`])
// 2 洗掉該行所有塊元素的資料源
fixedBlocks[`${line}_${i}`] = null
}
// 更新當前分數
score += COL_COUNT
document.getElementById("current-score").innerHTML = score
downLine(line)
}
// 讓被清理行之上的塊元素下落
function downLine(line) {
// 1 被清理行之上的所有塊元素資料源所在行數 + 1
// 2 讓塊元素在容器中的位置下落
// 3 清理之前的塊元素
// 遍歷被清理行之上的所有行
for (let i = line - 1; i >= 0; i--) {
// 該行中的所有列
for (let j = 0; j < COL_COUNT; j++) {
if (!fixedBlocks[`${i}_${j}`]) continue
// 存在資料
// 1 被清理行之上的所有塊元素資料源所在行數 + 1
fixedBlocks[`${i + 1}_${j}`] = fixedBlocks[`${i}_${j}`]
// 2 讓塊元素在容器中的位置下落
fixedBlocks[`${i + 1}_${j}`].style.top = (i + 1) * STEP + "px"
// 3 清理之前的塊元素
fixedBlocks[`${i}_${j}`] = null
}
}
}
// 讓模型自動下落
function autoDown() {
if (mInterval) {
clearInterval(mInterval)
}
mInterval = setInterval(() => {
move(0, 1)
}, 600)
}
// 判斷游戲結束
function isGameOver() {
// 當第0行存在塊元素的時候,表示游戲結束了
for (let i = 0; i < COL_COUNT; i++) {
if (fixedBlocks[`0_${i}`]) return true
}
return false
}
// 結束掉游戲
function gameOver() {
// 1 停止定時器
if (mInterval) {
clearInterval(mInterval)
}
// 重置最高分數
maxScore = Math.max(maxScore, score)
document.getElementById("max-score").innerHTML = maxScore
// 2 彈出對話框
alert("大吉大利,今晚吃雞!")
}
// 重新開始
function reset() {
const container = document.getElementById("container")
const childs = container.childNodes;
for (let i = childs.length - 1; i >= 0; i--) {
container.removeChild(childs[i]);
}
fixedBlocks = {}
score = 0
document.getElementById("current-score").innerHTML = score
init()
}
</script>
</body>
</html>
轉載于:https://blog.csdn.net/wanghuan1020/article/details/111473709
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/239002.html
標籤:其他
上一篇:js檔案替換
下一篇:TS vs JS基礎型別
