<!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>
<style type="text/css">
.container{
width: 200px;
height: 360px;
background-color: antiquewhite;
position: relative;
}
.activity_model{
width: 20px;
height: 20px;
background-color: cornflowerblue;
border: .3px solid rgb(148, 170, 218);
box-sizing: border-box;
position: absolute;
}
.fixed_model{
width: 20px;
height: 20px;
background-color: rgb(75, 79, 92);
border: .3px solid rgb(162, 167, 177);
box-sizing: border-box;
position: absolute;
}
</style>
</head>
<body onload="init();">
<!-- 背景容器 -->
<div id="container" class="container">
<!-- 块元素 -->
</div>
<script>
//创建一个常量控制步长
const STEP = 20;
//定义常量分割容器
const ROW_COUNT = 18,COL_COUNT = 10;
//创建每个模型的数据源(L型)
const MODELS = [
{
0:{
row:2,
col:0
},
1:{
row:2,
col:1
},
2:{
row:2,
col:2
},
3:{
row:1,
col:2
}
}
];
//创建变量,存放当前使用的模型
var currentModel = {};
//创建变量标记16宫格的位置
var currentX = 0,currentY = 0;
//记录所有块元素的位置
//k=行_列 : v=块元素
var fixedBlocks = {};
//入口方法
function init(){
createModel();
onKeyDown();
}
//根据模型的数据创建对应的块元素
function createModel(){
//确定当前使用哪一个模型
currentModel = MODELS[0];
//重新初始化16宫格的位置
currentX = 0;
currentY = 0;
//生成对应数量的块元素
for (var key in currentModel){
var divEle = document.createElement("div");
divEle.className = "activity_model";
document.querySelector("#container").appendChild(divEle);
}
//定位块元素的位置
locationBlock();
}
//根据数据源定位块元素的位置
function locationBlock(){
//判断块元素的越界行为
checkBound();
//1.拿到所有的块元素
var eles = document.querySelectorAll(".activity_model");
for (var i = 0;i<eles.length;i++){
//单个块元素
var activityModelEle = eles[i];
//2.找到每个块元素对应的数据
var blockModel = currentModel[i];
//3.根据每个块元素对应的数据来指定块元素的位置
//每个块元素的位置由两个条件决定:1、16宫格所在的位置。 2、块元素在16宫格中的位置
activityModelEle.style.left = (currentX + blockModel.col) * STEP + "px";
activityModelEle.style.top = (currentY + blockModel.row) * STEP + "px";
}
}
//监听用户键盘事件
function onKeyDown(){
document.onkeydown = function(evnet){
switch(event.keyCode){
case 38:
console.log("上");
rotate();
break;
case 39:
console.log("右");
move(1,0);
break;
case 40:
console.log("下");
move(0,1);
break;
case 37:
console.log("左");
move(-1,0);
break;
}
}
}
//控制块元素移动
function move(x,y){
if (isMeet(currentX + x,currentY+y,currentModel)){
//底部的触碰发生在移动16宫格的时候,并且这次移动是因为Y轴的变化引起的
if (y!=0){
//模型之间底部发生触碰
fixedBottomModel();
}
return;
}
//16宫格在动
currentX += x;
currentY += y;
//根据16宫格来重新定位块元素
locationBlock();
}
//控制模型的旋转
function rotate(){
//克隆一下currentModel,这里利用JSON方法完全复制一个currentModel
var json_obj = JSON.stringify(currentModel);
var cloneCurrentModel = JSON.parse(json_obj);
//算法
//旋转后的行 == 旋转前的列
//旋转后的列 == 3-旋转前的行
//遍历我们当前的 模型数据源
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;
locationBlock();
}
//控制模型只能在容器中移动
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.改变模型(中块元素)的样式
//2.让模型不可以再进行移动
var activityModelEles = document.querySelectorAll(".activity_model");
for (var i = activityModelEles.length - 1;i >= 0;i--){
//更改每个块元素的类名
//由于类名被更改,我们模型移动依靠的就是修改activity_model类的定位,现在不是原类名,自然就无法移动了
activityModelEles[i].className = "fixed_model";
//记录块元素的位置
var blockModel = currentModel[i];
fixedBlocks[(currentY + blockModel.row) + "_" + (currentX + blockModel.col)] = activityModelEles[i];
}
//判断是否要铺满一行需要清理
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 = 1;
//遍历当前行中的所有列
for (var j = 0; j < COL_COUNT; j++){
//如果当前行中有一列没有数据,那就说明没有被铺满
if (!fixedBlocks[i + "_" + j]){
flag = 0;
break;
}
}
if (flag){
//该行已经被铺满了,删除改行
removeLine(i);
}
}
}
//清理被铺满的一行
function removeLine(line){
//遍历该行中的所有列
for (var i = 0; i < COL_COUNT; i++){
//1.删除该行中所有的块元素
document.querySelector("#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;
}
}
}
</script>
</body>
</html>