黑马Bilibili
流程
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcss.com/lodash.js/4.17.4/lodash.min.js"></script>
<!-- 父容器使用 相对 位置,子容器选择 绝对 位置 -->
<style>
.container{
width: 200px;
height: 360px;
background: url("") no-repeat;
background-size: 200px 360px;
position: relative;
}
.activity_mode{
width: 20px;
height: 20px;
/* background-color: cadetblue; */
border: .3px solid #333333;
box-sizing: border-box;
position: absolute;
background: #ffd75e url("") center no-repeat;
background-size: 15px;
}
.fixed_mode{
width: 20px;
height: 20px;
background-color: #fefefe;
border: .3px solid #333;
box-sizing: border-box;
position: absolute;
background: #bbbbbb url("") center no-repeat;
background-size: 15px;
}
</style>
</head>
<body onload="init()">
<!-- 背景容器 -->
<div id ="container" class="container">
<!-- <div class="activity_mode"></div> -->
</div>
</body>
<script>
// 常量
// 每次移动的距离,步长
var STEP = 20;
// 分割容器 18行, 10列
var ROW_COUNT = 18,
COL_COUNT = 10;
// 创建每个模型的数据源
var MODELS = [
// 第一个模型数据源(L型)
/* 在4*4中画一个L型,坐标起始是0
0 0 0 0
0 0 1 0
1 1 1 0
0 0 0 0
*/
{
0: {
row: 2,
col: 0
},
1: {
row: 2,
col: 1
},
2: {
row: 2,
col: 2
},
3: {
row: 1,
col: 2
},
},
// 第二个模型数据源(凸型)
{
0: {
row: 1,
col: 1
},
1: {
row: 0,
col: 0
},
2: {
row: 1,
col: 0
},
3: {
row: 2,
col: 0
},
},
// 第三个模型数据源(田型)
{
0: {
row: 1,
col: 1
},
1: {
row: 2,
col: 1
},
2: {
row: 1,
col: 2
},
3: {
row: 2,
col: 2
},
},
// 第四个模型数据源(一型)
{
0: {
row: 0,
col: 0
},
1: {
row: 0,
col: 1
},
2: {
row: 0,
col: 2
},
3: {
row: 0,
col: 3
},
},
// 第五个模型数据源(Z型)
{
0: {
row: 1,
col: 1
},
1: {
row: 1,
col: 2
},
2: {
row: 2,
col: 2
},
3: {
row: 2,
col: 3
},
},
]
// 变量
// 当前使用的模型
var currentModel = {};
// 标记16宫格的位置
var currentX = 0,
currentY = 0;
// 记录所有块元素的位置
// K=行_列 : V=块元素
var fixedBlocks = {};
//定时器
var mInterval = null;
// 入口方法
function init(){
createModel();
onKeyDown();
}
// 根据模型的数据源创建对应的块元素
function createModel(){
// 判断游戏是否结束
if(isGameOver()){
gameOver();
}
// 确定当前使用哪一个模型
currentModel = MODELS[_.random(0, MODELS.length-1)];
// 重新初始化 16宫格的位置
currentX = 0;
currentY = 0;
// 生成对应数量的块元素
for(var key in currentModel){
// console.log("key: "+key);
var divEle = document.createElement("div");
divEle.className = "activity_mode";
// 将模型添加到背景容器中
document.getElementById("container").appendChild(divEle);
}
// 定位块元素的位置
locationBlocks();
// 模型自动下落
autoDown();
}
// 根据数据源定位块元素的位置
function locationBlocks(){
// 判断元素越界并处理
checkBound();
//1. 拿到所有的块元素
var eles = document.getElementsByClassName("activity_mode");
for(var i=0; i<eles.length; i++){
//单个块元素
var activityModelEle = eles[i];
//2. 找到每个块元素对应的数据
var blockModel = currentModel[i];
//3. 根据每个块元素对应的数据来指定块元素的位置
//每个块元素的位置由:1)16宫格所在位置 2)块元素在16宫格中的位置 两个元素确定
activityModelEle.style.top = (currentY + blockModel.row)*STEP + "px";
activityModelEle.style.left = (currentX + blockModel.col)*STEP + "px";
}
}
// 监听用户的键盘事件
function onKeyDown(){
document.onkeydown = function(event){
//console.log(event.keyCode);
switch(event.keyCode){
case 37:
//console.log("左");
move(-1,0);
break;
case 38:
//console.log("上");
rotate();
//move(0,-1);
break;
case 39:
//console.log("右");
move(1,0);
break;
case 40:
//console.log("下");
move(0,1);
break;
}
}
}
// 移动
function move(x, y){
//控制块元素移动
// var activityModelEle = document.getElementsByClassName("activity_mode")[0];
// // 初始 top 为空时,取0:parseInt(activityModelEle.style.top || 0)
// activityModelEle.style.top = parseInt(activityModelEle.style.top || 0) + y*STEP + "px";
// activityModelEle.style.left = parseInt(activityModelEle.style.left || 0) + x*STEP + "px";
if(isMeet(currentX+x, currentY+y, currentModel)){
// 底部的触碰发生在移动16宫格的时候,并且此次移动是因为Y轴变化引起的
if(y !== 0){
// 模型之间底部发生碰撞
fixedBottomModel();
}
return;
}
//模型的移动,有16宫格的移动表示
currentX += x;
currentY += y;
//根据16宫格的位置 重新定位块元素
locationBlocks();
}
// 旋转模型
function rotate(){
// 旋转后的行 = 旋转前的列
// 旋转后的列 = 3 - 旋转前的行
// 克隆 currentModel
var cloneCurrentModel = _.cloneDeep(currentModel);
// 遍历 模型数据源
for(var key in cloneCurrentModel){
// 块元素的数据源
var blockModel = cloneCurrentModel[key];
// 实现
var temp = blockModel.row;
blockModel.row = blockModel.col;
blockModel.col = 3 - temp;
}
//如果旋转之后发生触碰,则不允许再旋转
if(isMeet(currentX, currentY, cloneCurrentModel)){
return;
}
// 没有碰撞 接受此次旋转
currentModel = cloneCurrentModel;
locationBlocks();
}
// 控制模型只能在容器中移动
function checkBound(){
// 定义模型可以活动的边界
var leftBound = 0,
rightBound = COL_COUNT,
bottomBound = ROW_COUNT;
// 当块元素超出边界之后,让16宫格后退一步
for(var key in currentModel){
// 块元素对应的数据
var blockModel = currentModel[key];
// 左侧越界
if((blockModel.col + currentX) < leftBound){
currentX++;
}
// 右侧越界
if((blockModel.col + currentX) >= rightBound){
currentX--;
}
// 底部越界
if((blockModel.row + currentY) >= bottomBound){
currentY--;
// 把模型固定在底部
fixedBottomModel();
}
}
}
// 把模型固定在底部
function fixedBottomModel(){
//1. 改变模型(每个块的)的样式(可移动时是亮色,不可移动变灰)
var activityModelEles = document.getElementsByClassName("activity_mode");
//每次修改className后,activityModelEles 会根据当前实际的个数更新 length(4->3->2->1)
//for(var i=0; i<activityModelEles.length; i++){ //报错
for(var i=activityModelEles.length-1; i>=0; i--){
// 拿到每个块元素
var activityModelEle = activityModelEles[i];
// 更改块元素的类明(修改样式)
//2. 让模型不可以再进行移动(移动时获取的是activity_mode样式)
activityModelEle.className = "fixed_mode";
// 把该块元素放入变量中
var blockModel = currentModel[i];
fixedBlocks[(currentY + blockModel.row) + "_" + (currentX + blockModel.col)]=activityModelEle;
}
// 判断某一行是否需要清理
isRemoveLine();
//3. 创建新的模型
createModel();
}
// 判断模型之间的触碰问题
// x, y 表示16宫格《将要》移动到的位置
// model 表示当前模型数据源《将要》完成的变化(还没变)
function isMeet(x, y, model){
// 所谓模型之间的触碰,在一个固定的位置已经存在一个被固定的块元素时,那么活动中的模型不能再占用该位置
// 判断触碰,就是再判断活动中的模型《将要移动到的位置》是否已经存在被固定的模型(块元素)
// 如果存在返回 true,否则返回 false。
for(var k in model){
var blockModel = model[k];
// 该位置是否已经存在块元素?
if(fixedBlocks[(y+blockModel.row)+"_"+(x+blockModel.col)]){
return true; //表示将要移动到的位置会发生碰撞
}
}
return false;
}
// 判断一行是否被铺满
function isRemoveLine(){
// 在一行中, 每一列都存在块元素,那么该行就需要被清理
// 遍历所有行中的列
// 遍历所有行
for(var i=0; i<ROW_COUNT; i++){
var flag = true;
for(var j=0; j<COL_COUNT; j++){
// 如果当前行中有一列没有数据,那么就说明当前行没有被铺满
if(!fixedBlocks[i+"_"+j]){ //取不到数据
flag = false;
break;
}
}
if(flag){
//该行已经被铺满
//console.log("该行已经被铺满");
removeLine(i);
}
}
}
// 清理被铺满的这一行
function removeLine(line){
// 遍历该行中的所有列
for(var i=0; i<COL_COUNT; i++){
// 1. 删除该行中的所有的块元素
document.getElementById("container").removeChild(fixedBlocks[line+"_"+i]);
// 2. 删除该行中所有块元素的数据源
fixedBlocks[line+"_"+i] = null;
}
downLine(line);
}
// 让被清理行之上的块元素下落
function downLine(line){
//遍历被清理行之上的所有行
for(var i=line-1; i>=0; i--){
//遍历该行中的所有列
for(var 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(function(){
move(0,1)
}, 600);
}
// 判断游戏结束
function isGameOver(){
// 当第0行存在块元素的时候,表示游戏结束
for(var i=0; i<COL_COUNT; i++){
if(fixedBlocks["0_"+i]){
return true;
}
}
return false;
}
// 结束游戏
function gameOver(){
//1. 停止定时器
if(mInterval){
clearInterval(mInterval);
}
//2. 弹出对话框
alert("Game Over!");
}
</script>
</html>