效果图

方块定位原理通过16宫格定位坐标,把坐标存到数组中去

[

[[2,0],[2,1],[2,2],[1,2]],//L

[[1,1],[2,1],[2,2],[2,3]], //左L

[[2,0],[2,1],[2,2],[1,1]],//凸

[[2,0],[2,1],[3,0],[3,1]],//田

[[2,0],[2,1],[2,2],[2,3]],//一

[[2,0],[2,1],[3,1],[3,2]],//Z

[[2,1],[2,2],[3,0],[3,1]]//左Z

]

html和css样式

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
.container{
height: 400px;
width: 370px;
border: 10px solid #333;
margin: 20px auto;
}
.game-bar{
width: 150px;
height: 400px;
float: left;
}
.game-map{
width: 200px;
height: 400px;
background: #ccc;
float: left;
border: 10px solid green;
position: relative;
top: -10px;
overflow: hidden;
} .tetris{
height: 20px;
width: 20px;
background: red;
border: 1px solid #eee;
box-sizing: border-box;
position: absolute; }
.fixed_tetris{
background: #333;
border: 1px solid #eee;
height: 20px;
width: 20px;
box-sizing: border-box;
position: absolute;
}
#level{
width: 20px;
text-align: center;
}
button{
margin: 20px;
}
</style>
<body>
<div class="container">
<div class="game-bar">
<div class="game-level">
难度: <input type="text" value="1" id = "level"> 只能1-9
</div>
<div class="integral">
积分:<span class="num"></span>
</div>
<button class="btn1">暂停</button>
<button class="btn2">开始</button>
<button class="btn3">重新开始</button>
</div>
<div class="game-map">
<!-- <div class="tetris">
</div> -->
</div>
</div>

js代码

// 创建一个构造函数
class Tetris{
constructor(){
this.val = document.querySelector('#level').value;//记录上一个关卡
this.newColor = randomColor();//随机色
this.whether = false;//判断是否通关
this.timer;//定时器返回值
this.step = 20;//每步移动的距离
this.sum = 0;//积分
this.sum1 = 0;//每次通关积分
this.row = 20;//行
this.col = 10;//列
this.model = [//方块模型
[[2,0],[2,1],[2,2],[1,2]],//L
[[1,1],[2,1],[2,2],[2,3]], //左L
[[2,0],[2,1],[2,2],[1,1]],//凸
[[2,0],[2,1],[3,0],[3,1]],//田
[[2,0],[2,1],[2,2],[2,3]],//一
[[2,0],[2,1],[3,1],[3,2]],//Z
[[2,1],[2,2],[3,0],[3,1]]//左Z
];
//标记16宫格
this.tetris = [];
this.tetrisX = 0;
this.tetrisY = 0;
//记录所以方块的位置
this.fixedModel = {};
} //功能
init(){
this.createTeiris();
this.location();
this.KeyDown();
this.integral();
this.incident();
} //创建俄罗斯方块
createTeiris(){
if(this.gameOver()){
if(this.timer){
clearInterval(this.timer);
}
alert('Game over');
return;
}
//确定当前使用的模型
this.tetris = this.model[randomNum(0, this.model.length-1)]; //初始化16宫格
this.tetrisX = randomNum(0, 7);
this.tetrisY = -2;
for(var i = 0, len = this.tetris.length; i < len; i++){
var node = document.createElement('div');
node.className = "tetris";
node.style.background = this.newColor;
document.querySelector('.game-map').appendChild(node); } this.randomRotate();
this.location();
this.autoMove();//自动下落
} //根据数据定位
location(){ this.border();
//拿到全部方块
this.tetrisBox = document.querySelectorAll('.tetris');
for(var i = 0, len = this.tetrisBox.length; i < len; i++){
//单个方块
let tetris = this.tetrisBox[i]; //定位,16宫格的位置和方块在16宫格中的位置
tetris.style.left = (this.tetrisX + this.tetris[i][1]) * this.step + 'px';
tetris.style.top = (this.tetrisY + this.tetris[i][0]) * this.step + 'px'; } } //旋转方块
rotate(){
var cloneTetris = deepClone(this.tetris); //深拷贝 //旋转后的行 = 旋转前的列 旋转后的列 = 3 - 旋转前的行
for(var i = 0, len = cloneTetris.length; i < len; i++){
//实现旋转
let num = cloneTetris[i][0];//获取旋转前的行
cloneTetris[i][0] = cloneTetris[i][1];
cloneTetris[i][1] = 3 - num; }
if(this.crash(this.tetrisX,this.tetrisY,cloneTetris)){
return;
} this.tetris = cloneTetris; this.location();
} //键盘事件
KeyDown(type){ document.onkeydown = (ev)=>{
var e = ev || event;//兼容
switch (e.keyCode){
case 38://上
this.rotate();
break;
case 39://右
this.move(1,0)
break;
case 40://下
this.move(0,1)
break;
case 37://左
this.move(-1,0)
break;
}
} }
//方块移动
move(x,y){ if(this.crash(this.tetrisX + x,this.tetrisY + y ,this.tetris)){
//底部触碰移动是由y轴移动
if(y !== 0){
this.fixedTetris();
}
return;
} //控制16宫格移动
this.tetrisX += x;
this.tetrisY += y; this.location();
}
//边框
border(){
//定义不能越界
for(var i = 0, len = this.tetris.length; i < len; i++){
//左边
if((this.tetris[i][1] + this.tetrisX) < 0){
this.tetrisX++;
}
//右边
if((this.tetris[i][1] + this.tetrisX) >= this.col){
this.tetrisX--;
}
//底部
if((this.tetris[i][0] + this.tetrisY) >= this.row){
this.tetrisY--;
this.fixedTetris();
}
}
}
//方块到底部
fixedTetris(){
//改变方块的样式并且不可以移动
var tetris = document.querySelectorAll('.tetris');
for(var i = 0, len = tetris.length; i < len; i++){//由于类名改变所以必须一次性获取len
//拿到单个方块
let node = tetris[i];
node.className = "fixed_tetris";
node.style.background = '#333';//固定后变颜色
//把位置放入数组
this.fixedModel[(this.tetrisY + this.tetris[i][0]) + '_' + (this.tetrisX + this.tetris[i][1])] = tetris[i];
}
this.removeLine();
this.newColor = randomColor();
this.createTeiris();
} //碰撞
crash(x,y,model){
//判断移动中的方块将要移动的位置是否存在已固定的方块
for(var i = 0, len = model.length; i < len; i++){
if(this.fixedModel[(y + model[i][0]) + '_' + (x + model[i][1])]){
return true;
}
}
return false;
}
//删除填满方块的一行
removeLine(){
//在一行中每一列都存在方块的话就清理并增加积分
//遍历行
for(var i = 0; i < this.row; i++){
var full = true;
//遍历列
for(var j = 0; j < this.col; j++){
//如果当前行中有一列没有数据,就说明没铺满
if(!this.fixedModel[i + '_' + j]){
full = false;
break;
}
}
if(full){
//清理这一行
for(var x = 0; x < this.col; x++){
document.querySelector('.game-map').removeChild(this.fixedModel[i + '_' + x]);
this.fixedModel[i + '_' + x] = null; }
this.downLine(i);
this.sum += 10;//积分+10
this.sum1 += 10;//积分+10
this.integral();
}
}
}
//让被清理之上的方块下落
downLine(line){
//遍历被清理之上的所以行
for(var i = line - 1; i >= 0; i--){
//遍历该行的列
for(var j = 0; j < this.col; j++){
if(!this.fixedModel[i + '_' + j]) continue;
//存在数据
//行数+1
this.fixedModel[(i + 1) + '_' + j] = this.fixedModel[i + '_' +j];
//让方块下落
this.fixedModel[(i + 1) + '_' + j].style.top = (i + 1) * this.step + 'px';
//清理之前的方块
this.fixedModel[i + '_' + j] = null;
}
}
} //自动下落
autoMove(){
var speed = this.level();
if(this.timer){
clearInterval(this.timer);
}
this.timer = setInterval(() => {
this.move(0,1)
}, speed);
} //积分
integral(){
let num = document.querySelector('.num');
num.innerHTML = this.sum; } gameOver(){
//当第0行存在方块
for(var i = 0; i < this.col; i++){
if(this.fixedModel['0_' + i]){
return true;
}
}
return false;
}
//随机生成方向
randomRotate(){
switch(randomNum(1, 4)){
case 2:
this.rotate();
break;
case 3:
this.rotate();
this.rotate();
break;
case 4:
this.rotate();
this.rotate();
this.rotate();
break;
default:
return;
}
}
//判断难度
level(){
var speed;
let lev = document.querySelector('#level');
if(this.sum1 == 100 && lev.value == 9){
alert('恭喜您通关了');
clearInterval(this.timer);
document.onkeydown = null;
this.whether = true;//已通关
} else if(this.sum1 == 100){//每100分过关一次
this.sum1 = 0;
lev.value++;
}
var rule = /^[0-9]$/;//不能输入1-9以外的
if(!rule.test(lev.value)){
alert('只能输入 1 到 9 ');
lev.value = this.val;
} else{
if(lev.value == 1){
speed = 600;
}
if(lev.value == 2){
speed = 500;
}
if(lev.value == 3){
speed = 450;
}
if(lev.value == 4){
speed = 400;
}
if(lev.value == 5){
speed = 300;
}
if(lev.value == 6){
speed = 200;
}
if(lev.value == 7){
speed = 150;
}
if(lev.value == 8){
speed = 100;
}
if(lev.value == 9){
speed = 50;
}
}
this.val = lev.value; return speed;
}
//事件
incident(){
var inp = document.querySelector('#level');
var btn1 = document.querySelector('.btn1');
var btn2 = document.querySelector('.btn2');
var btn3 = document.querySelector('.btn3');
inp.onblur = ()=>{ if(inp.value != this.val){
this.sum1 = 0;//换关卡重新归零
}
this.level();//失去焦点后判断
}
btn1.onclick = ()=>{
clearInterval(this.timer);//清除计时器
document.onkeydown = null;//键盘事件清空
}
btn2.onclick = () =>{
this.autoMove();//启动计时器
this.KeyDown();//启动键盘事件
if(this.gameOver() || this.whether){
this.startAgain();
}
}
btn3.onclick = () =>{
this.startAgain();
}
}
startAgain(){
for(var i = 0; i < this.row; i++){
for(var j = 0; j < this.col; j++){
this.fixedModel[i + '_' +j] = null;//清空所有数据
}
}
document.querySelector('.game-map').innerHTML = '';//清空节点
this.createTeiris();//重新启动
this.sum = 0;
this.sum1 = 0;
this.whether = false;
this.integral();
} } //封装一个深拷贝的方法
function deepClone(obj) {
var _obj = JSON.stringify(obj),
cloneObj = JSON.parse(_obj);
return cloneObj;
}
//封装随机数
function randomNum(min, max){
var tmp = max - min + 1;
return parseInt(Math.random() * tmp) + min
}
//随机颜色
function randomColor(){
var color = ["red","blue","green","yellow","#00FFFF","#930093","#F80000","#984B4B"];
return color[randomNum(0, color.length - 1)];
} new Tetris().init();

原生js俄罗斯方块的更多相关文章

  1. 原生JS封装Ajax插件(同域&&jsonp跨域)

    抛出一个问题,其实所谓的熟悉原生JS,怎样的程度才是熟悉呢? 最近都在做原生JS熟悉的练习... 用原生Js封装了一个Ajax插件,引入一般的项目,传传数据,感觉还是可行的...简单说说思路,如有不正 ...

  2. 常用原生JS方法总结(兼容性写法)

    经常会用到原生JS来写前端...但是原生JS的一些方法在适应各个浏览器的时候写法有的也不怎么一样的... 今天下班有点累... 就来总结一下简单的东西吧…… 备注:一下的方法都是包裹在一个EventU ...

  3. 原生JS实现"旋转木马"效果的图片轮播插件

    一.写在最前面 最近都忙一些杂七杂八的事情,复习软考.研读经典...好像都好久没写过博客了... 我自己写过三个图片轮播,一个是简单的原生JS实现的,没有什么动画效果的,一个是结合JQuery实现的, ...

  4. 再谈React.js实现原生js拖拽效果

    前几天写的那个拖拽,自己留下的疑问...这次在热心博友的提示下又修正了一些小小的bug,也加了拖拽的边缘检测部分...就再聊聊拖拽吧 一.不要直接操作dom元素 react中使用了虚拟dom的概念,目 ...

  5. React.js实现原生js拖拽效果及思考

    一.起因&思路 不知不觉,已经好几天没写博客了...近来除了研究React,还做了公司官网... 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖 ...

  6. 原生JS实现全屏切换以及导航栏滑动隐藏及显示——重构前

    思路分析: 向后滚动鼠标滚轮,页面向下全屏切换:向前滚动滚轮,页面向上全屏切换.切换过程为动画效果. 第一屏时,导航栏固定在页面顶部,切换到第二屏时,导航条向左滑动隐藏.切换回第一屏时,导航栏向右滑动 ...

  7. 原生js实现autocomplete插件

    在实际的项目中,能用别人写好的插件实现相关功能是最好不过,为了节约时间成本,因为有的项目比较紧急,没充分时间让你自己来写,即便写了,你还要花大量时间调试兼容性.但是出于学习的目的,你可以利用闲暇时间, ...

  8. 原生js封装ajax:传json,str,excel文件上传表单提交

    由于项目中需要在提交ajax前设置header信息,jquery的ajax实现不了,我们自己封装几个常用的ajax方法. jQuery的ajax普通封装 var ajaxFn = function(u ...

  9. 原生JS实现购物车结算功能代码+zepto版

    html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3 ...

随机推荐

  1. python函数版ATM

    最近系统的学习python函数知识点,感觉在面向对象之前,函数的功能确实强大. 最近使用函数写了ATM项目,虽然需求简单但也有很多知识点需要注意,这个项目把python基础的很多知识点都用上了. 前前 ...

  2. Vysor Pro1.9.3破解,连接 USB 数据线在电脑上远程控制 Android 手机平板/同步显示画面

    Vysor PRO 破解方法 1.下载Vysor Pro,   Vysor Pro下载地址 ,chrome版需要挂梯子. 下载后,能连接,但是清晰度太低,能使用的功能也很少,下面我们就开始来破解它. ...

  3. JAVA开发中如何优化类的设计

    具体类依赖于抽象类,而非抽象类依赖于具体类.这样做有利于一个抽象类扩展多个具体类. 开放封闭原则:对扩展开放,对修改封闭. 1.永远保持数据私有 保持数据的私有是设计类时,必须重点考虑的问题.保持私有 ...

  4. 初探elasticsearch

    目录 安装elasticsearch elasticsearch中的层级结构与关系型数据库的对比 elasticsearch的分布式特性 集群和节点 为java用户提供的两种内置客户端 节点客户端(n ...

  5. 报错:Error instantiating class com.liwen.mybatis.bean.Employee with invalid types () or values ().

    实体类默认构造方法是无参构造方法,一旦重写构造方法,默认方法就会变成重写之后的构造方法,所以该错误报的错就是实体类缺少无参构造方法

  6. tpyboard v202 测试tcp通讯,i2c的oled程序,呼吸灯源码,希望对大家有所帮助

    1.下载到板子里的main.py代码如果需要驱动oled的,可以参考我上面那篇文章import time, mathimport machineimport network# from ssd1306 ...

  7. 【转】.strip().split('t')和.strip().split()

    https://blog.csdn.net/qq_35290785/article/details/94780620 .strip().split('t')line =' nihao, zhenhao ...

  8. OpenCV-Python 理解SVM | 五十五

    目标 在这一章中 我们将对SVM有一个直观的了解 理论 线性可分数据 考虑下面的图像,它具有两种数据类型,红色和蓝色.在kNN中,对于测试数据,我们用来测量其与所有训练样本的距离,并以最小的距离作为样 ...

  9. php 设置允许跨域请求

    php 服务端代码 <?php header('Content-Type: text/html;charset=utf-8'); header('Access-Control-Allow-Ori ...

  10. ThunderNet :像闪电一样,旷视再出超轻量级检测器,高达267fps | ICCV 2019

    论文提出了实时的超轻量级two-stage detector ThunderNet,靠着精心设计的主干网络以及提高特征表达能力的CEM和SAM模块,使用很少的计算量就能超越目前的one-stage d ...