d3.js是一个不错的可视化框架,同时对于操作dom也是十分方便的。今天我们使用d3.js配合es6的类来制作一个童年小游戏--俄罗斯方块。话不多说先上图片。

1. js tetris类

由于方法拆分的比较细所以加上了一些备注(这不是我的风格!)

const graphMap = [
{
name: '倒梯形',
position: [[0,4],[1,3],[1,4],[1,5]],
rotate: [[[0,0],[-2,0],[-1,-1],[0,-2]],[[1,0],[1,2],[0,1],[-1,0]],[[-1,0],[1,0],[0,1],[-1,2]],[[0,0],[0,-2],[1,-1],[2,0]]],
color: '#D7DF01'
},
{
name: '一字型',
position: [[0,3],[0,4],[0,5],[0,6]],
rotate: [[[-1,1],[0,0],[1,-1],[2,-2]],[[1,2],[0,1],[-1,0],[-2,-1]],[[2,-2],[1,-1],[0,0],[-1,1]],[[-2,-1],[-1,0],[0,1],[1,2]]],
color: '#0000FF'
},
{
name: '正方形',
position: [[0,4],[0,5],[1,4],[1,5]],
rotate: [[[0,0],[0,0],[0,0],[0,0]],[[0,0],[0,0],[0,0],[0,0]],[[0,0],[0,0],[0,0],[0,0]],[[0,0],[0,0],[0,0],[0,0]]],
color: '#FF0000'
},
{
name: 'Z字型',
position: [[0,3],[0,4],[1,4],[1,5]],
rotate: [[[0,1],[1,0],[0,-1],[1,-2]],[[1,1],[0,0],[-1,1],[-2,0]],[[1,-2],[0,-1],[1,0],[0,1]],[[-2,0],[-1,1],[0,0],[1,1]]],
color: '#800080'
},
{
name: '反Z字型',
position: [[1,3],[1,4],[0,4],[0,5]],
rotate: [[[-1,1],[0,0],[1,1],[2,0]],[[0,1],[-1,0],[0,-1],[-1,-2]],[[2,0],[1,1],[0,0],[-1,1]],[[-1,-2],[0,-1],[-1,0],[0,1]]],
color: '#FFA500'
},
{
name: 'L字型',
position: [[1,3],[0,3],[0,4],[0,5]],
rotate: [[[-1,1],[0,2],[1,1],[2,0]],[[0,1],[1,0],[0,-1],[-1,-2]],[[1,-1],[0,-2],[-1,-1],[-2,0]],[[0,-1],[-1,0],[0,1],[1,2]]],
color: '#90EE90'
},
{
name: '反L字型',
position: [[0,3],[0,4],[0,5],[1,5]],
rotate: [[[-1,2],[0,1],[1,0],[0,-1]],[[2,0],[1,-1],[0,-2],[-1,-1]],[[1,-2],[0,-1],[-1,0],[0,1]],[[-2,0],[-1,1],[0,2],[1,1]]],
color: '#AEEBFF'
}
]
class Tetris {
constructor() {
this._grid = [];
this._rows = 18;
this._cols = 10;
this._div = 33;
this._nextDiv = 15;
this._duration = 1000;
this._width = this._div * this._cols;
this._height = this._div * this._rows;
this._svg = null;
this._nextSvg = null;
this._timeout = null;
this._time = null;
this._showGrid = false;
this._haveArray = [];
this._curtArray = [];
this._colors = '';
this._rotateIndex = 0;
this._rotateArray = [];
this._fixedColor = '#666';
this._nextNumber = 0;
this._graphMap = graphMap;
this._level = 1;
this._levelLimit = [0,20,50,90,140,200,270,350,440,540,650,770,900,1040,1190,1350,1520];
this._score = 0;
this._timeNumber = 0;
this.initSvg();
this.initNextSvg();
this.addKeyListener();
}
initSvg() {
this._svg = d3.select('.svg-container')
.append('svg')
.attr('width', this._width)
.attr('height', this._height)
.attr('transform', 'translate(0, 4)')
}
initNextSvg() {
this._nextSvg = d3.select('.next')
.append('svg')
.attr('width', 100)
.attr('height', 60)
}
toggleGrid() {
if(this._showGrid) {
this._showGrid = false;
d3.select('g.grid').remove();
} else {
this._showGrid = true;
this._grid = this._svg.append('g')
.attr('class', 'grid')
this._grid.selectAll('line.row')
.data(d3.range(this._rows))
.enter()
.append('line')
.attr('class', 'row')
.attr('x1', 0)
.attr('y1', d => d * this._div)
.attr('x2', this._width)
.attr('y2', d => d * this._div)
this._grid.selectAll('line.col')
.data(d3.range(this._cols))
.enter()
.append('line')
.attr('class', 'col')
.attr('x1', d => d * this._div)
.attr('y1', 0)
.attr('x2', d => d * this._div)
.attr('y2', this._height)
}
}
addKeyListener() {
d3.select('body').on('keydown', () => {
switch (d3.event.keyCode) {
case 37:
this.goLeft();
break;
case 38:
this.rotate();
break;
case 39:
this.goRight();
break;
case 40:
this.goDown();
break;
case 32:
console.log('空格');
break;
case 80:
console.log('暂停');
break;
default:
break;
}
})
}
//设置运动图形 如果仍有掉落空间则继续掉落 反之调用setHaveArray
initGraph() {
this.renderGraph();
this._timeout = setTimeout(() => {
if(this.canDown()) {
this.downArray();
this.initGraph();
} else {
this.setHaveArray();
if(!this.gameOver()) {
this.randomData();
this.nextGraphNumber();
this.initGraph();
} else {
clearTimeout(this._time);
d3.select('#modal').style('top', '0px')
}
}
}, this._duration * (1 - ((this._level - 1) / this._levelLimit.length) / 2))
}
//渲染图形
renderGraph() {
this._svg.selectAll('rect.active').remove();
this._svg.selectAll('rect.active')
.data(this._curtArray)
.enter()
.append('rect')
.attr('class', 'active')
.attr('x', d => this._div * d[1] + 1)
.attr('y', d => this._div * d[0] + 1)
.attr('width', this._div - 3)
.attr('height', this._div - 3)
.attr('stroke', this._color)
.attr('stroke-width', 2)
.attr('fill', this._color)
.attr('fill-opacity', 0.5)
}
//设置掉落后的数组,并清除运动的图形 重置状态
setHaveArray() {
this._curtArray.forEach(d => this._haveArray.push(d));
this._svg.selectAll('rect.active').attr('class', 'fixed').attr('fill', this._fixedColor).attr('fill-opacity', 0.5).attr('stroke', this._fixedColor);
this._rotateIndex = 0;
this.clearLines();
}
//检测有满列 然后消除
clearLines() {
let clearLinesArr = [];
let allRowsObj = {};
let temp = [];
let arr = this._haveArray.map(d => d[0]);
arr.forEach(d => {
if(allRowsObj.hasOwnProperty(d)) {
allRowsObj[d] ++
} else {
allRowsObj[d] = 1;
}
})
for(var i in allRowsObj) {
if(allRowsObj[i] == this._cols) {
clearLinesArr.push(i)
}
}
if(clearLinesArr.length != 0) {
this.setScoreAndLevel(clearLinesArr.length);
this._haveArray = this._haveArray.filter(a => !clearLinesArr.some(b => b == a[0]));
this._haveArray = this._haveArray.map(d => [this.downSome(d[0],clearLinesArr), d[1]])
this._svg.selectAll('rect.fixed').remove();
this._svg.selectAll('rect.fixed').data(this._haveArray)
.enter()
.append('rect')
.attr('class', 'fixed')
.attr('x', d => this._div * d[1] + 1)
.attr('y', d => this._div * d[0] + 1)
.attr('width', this._div - 3)
.attr('height', this._div - 3)
.attr('stroke', this._fixedColor)
.attr('stroke-width', 2)
.attr('fill', this._fixedColor)
.attr('fill-opacity', 0.5)
}
}
//消除时 判断下落层数
downSome(c, arr) {
let num = 0;
arr.forEach(d => {
if(c < d) {
num ++;
}
})
return num + c;
}
//设置等级和分数
setScoreAndLevel(num) {
switch(num) {
case 1:
this._score = this._score + 1;
break;
case 2:
this._score = this._score + 3;
break;
case 3:
this._score = this._score + 6;
break;
case 4:
this._score = this._score + 10;
default:
break;
}
for(var i=0; i<this._levelLimit.length; i++) {
if(this._score <= this._levelLimit[i]) {
this._level = i + 1;
break;
}
}
d3.select('#score').html(this._score);
d3.select('#level').html(this._level);
}
//左移动
goLeft() {
if(this.canLeft()) {
this.leftArray();
this.renderGraph();
}
}
//右移动
goRight() {
if(this.canRight()) {
this.rightArray();
this.renderGraph();
}
}
//旋转
rotate() {
if(this.canRotate()) {
this.rotateArray();
this.renderGraph();
}
}
//下移动
goDown() {
if(this.canDown()) {
this.downArray();
this.renderGraph();
}
}
//下落更新数组
downArray() {
this._curtArray = this._curtArray.map(d => {
return [d[0] + 1, d[1]]
})
}
//左移更新数组
leftArray() {
this._curtArray = this._curtArray.map(d => {
return [d[0], d[1] - 1]
})
}
//右移更新数组
rightArray() {
this._curtArray = this._curtArray.map(d => {
return [d[0], d[1] + 1]
})
}
//旋转更新数组
rotateArray() {
let arr = this._rotateArray[this._rotateIndex];
this._curtArray = this._curtArray.map((d,i) => {
return [d[0] + arr[i][0], d[1] + arr[i][1]]
})
this._rotateIndex = (this._rotateIndex + 1) % 4;
}
//判断是否可以下落
canDown() {
let max = 0;
let status = true;
let nextArr = this._curtArray.map(d => {
if(d[0] + 1 > max) {
max = d[0];
}
return [d[0] + 1, d[1]]
});
nextArr.forEach(d => {
this._haveArray.forEach(item => {
if(item[0] == d[0] && item[1] == d[1]) {
status = false;
}
})
})
if(!status || max > 16) {
return false;
} else {
return true;
}
}
//判断是否可以左移
canLeft() {
let min = this._cols;
let status = true;
let nextArr = this._curtArray.map(d => {
if(d[1] - 1 < min) {
min = d[1];
}
return [d[0], d[1] - 1]
})
nextArr.forEach(d => {
this._haveArray.forEach(item => {
if(item[0] == d[0] && item[1] == d[1]) {
status = false;
}
})
})
if(!status || min <= 0) {
return false;
} else {
return true;
}
}
//判断是否可以右移
canRight() {
let max = 0;
let status = true;
let nextArr = this._curtArray.map(d => {
if(d[1] + 1 > max) {
max = d[1];
}
return [d[0], d[1] + 1]
})
nextArr.forEach(d => {
this._haveArray.forEach(item => {
if(item[0] == d[0] && item[1] == d[1]) {
status = false;
}
})
})
if(!status || max > this._cols - 2) {
return false;
} else {
return true;
}
}
//判断可以变形
canRotate() {
let max = 0;
let min = this._cols;
let status = true;
let arr = this._rotateArray[this._rotateIndex];
let nextArr = this._curtArray.map((d,i) => {
if(d[1] + 1 > max) {
max = d[1];
}
if(d[1] - 1 < min) {
min = d[1];
}
return [d[0] + arr[i][0], d[1] + arr[i][1]]
})
if(!status || max > this._cols - 1 || min < 0) {
return false;
} else {
return true;
}
}
//判断游戏结束
gameOver() {
let status = false;
this._haveArray.forEach(d => {
if((d[0] == 0 && d[1] == 3) || (d[0] == 0 && d[1] == 4) || (d[0] == 0 && d[1] == 5) || (d[0] == 0 && d[1] == 6)) {
status = true;
}
})
return status;
}
//随机生成图形块
randomData() {
this._curtArray = this._graphMap[this._nextNumber].position;
this._color = this._graphMap[this._nextNumber].color;
this._rotateArray = this._graphMap[this._nextNumber].rotate;
}
//预设下一个图形展示
nextGraphNumber() {
let rand = [0,0,1,1,2,2,3,4,5,6];
this._nextNumber = rand[Math.floor(Math.random() * 10000) % 10];
this._nextSvg.selectAll('rect.ne').remove();
this._nextSvg.selectAll('rect.ne')
.data(this._graphMap[this._nextNumber].position)
.enter()
.append('rect')
.attr('class', 'ne')
.attr('x', d => this._nextDiv * (d[1] - 1) + 1)
.attr('y', d => this._nextDiv * (d[0] + 1) + 1)
.attr('width', this._nextDiv - 3)
.attr('height', this._nextDiv - 3)
.attr('stroke', this._graphMap[this._nextNumber].color)
.attr('stroke-width', 2)
.attr('fill', this._graphMap[this._nextNumber].color)
.attr('fill-opacity', 0.5)
}
//初始化数据
initData() {
this._haveArray = [];
this._level = 1;
this._score = 0;
this._timeNumber = 0;
this._svg.selectAll('rect').remove();
d3.select('#score').html(0);
d3.select('#level').html(1);
d3.select('#time').html(0);
}
//开始时间
initTime() {
this._time = setInterval(() => {
this._timeNumber ++;
d3.select('#time').html(this._timeNumber);
},1000)
}
//开始游戏
startGame() {
this.initData();
this.randomData();
this.nextGraphNumber();
this.initGraph();
this.initTime();
}
}

2. css 代码

* {
padding:;
margin:;
}
body {
width: 480px;
margin: 30px auto;
}
.svg-container {
overflow: hidden;
border: 5px solid rgba(0,0,0,0.2);
width: 330px;
position: relative;
float: left;
}
#modal {
position: absolute;
top: 0px;
background-color: white;
border-bottom: 5px solid rgb(202,202,202);
padding: 20px;
width: 310px;
text-align: center;
z-index:;
transition: 200ms linear;
}
#newGame {
text-decoration: none;
color: gray;
font-size: 25px;
cursor: pointer;
}
aside {
position: relative;
float: right;
}
aside .next {
width: 100px;
height: 60px;
padding: 10px;
border: 5px solid rgba(0,0,0,0.2);
border-radius: 2px;
margin-bottom: 10px;
}
aside .score {
width: 100px;
padding: 10px;
border: 5px solid rgba(0,0,0,0.2);
border-radius: 2px;
color: gray;
}
aside .pause {
color: gray;
font-size: 12px;
font-style: italic;
padding-left: 3px;
margin-top: 15px;
}
.row {
stroke: lightgray;
}
.col {
stroke: lightgray;
}

3. html代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
<link rel="stylesheet" type="text/css" href="css/base.css"/>
<script type="text/javascript" src="js/d3.v4.js"></script>
<script type="text/javascript" src="js/base.js"></script>
</head>
<body>
<div class="container">
<div class="svg-container">
<div id="modal" class="active">
<span id="newGame" onclick="newGame()">New Game</span>
</div>
</div>
<aside>
<div class="next"></div>
<div class="score">
<table>
<tr>
<td>Level:</td>
<td id="level"></td>
</tr>
<tr>
<td>Score:</td>
<td id="score"></td>
</tr>
<tr>
<td>Time:</td>
<td id="time"></td>
</tr>
</table>
</div>
<div class="pause">
<input type="checkbox" onclick="toggleGrid()"/> 网格
</div>
</aside>
</div>
<script>
var tetris = new Tetris(); function toggleGrid() {
tetris.toggleGrid()
}
function newGame() {
document.getElementById('modal').style.top = '-100px';
tetris.startGame()
}
</script>
</body>
</html>

想预览或者下载demo的人请移步至原文

原文地址 http://www.bettersmile.cn

d3.js 制作简单的俄罗斯方块的更多相关文章

  1. d3.js 制作简单的贪吃蛇

    d3.js是一个不错的可视化框架,同时对于操作dom也是十分方便的.今天我们使用d3.js配合es6的类来制作一个童年小游戏–贪吃蛇.话不多说先上图片. 1. js snaker类 class Sna ...

  2. D3.js 制作中国地图 .net 公共基础类

    D3.js 制作中国地图 from:  http://d3.decembercafe.org/pages/map/index.html GeoJSON is a format for encoding ...

  3. js实现简单的俄罗斯方块小游戏

    js实现简单的俄罗斯方块小游戏 开始 1. 创建一个宽为 200px,高为 360px 的背景容器 <!DOCTYPE html> <html lang="en" ...

  4. d3.js制作连线动画图和编辑器

    此文章为原创文章,原文地址:https://www.cnblogs.com/eagle1098/p/11431679.html 连线动画图 编辑器 效果如上图所示.本项目使用主要d3.jsv4制作,分 ...

  5. d3.js制作条形时间范围选择器

    此文章为原创文章,原文地址:https://www.cnblogs.com/eagle1098/p/12146688.html 效果如上图所示. 本项目使用主要d3.js v4制作,可以用来选择两年的 ...

  6. d3.js制作蜂巢图表带动画效果

    以上是效果图,本图表使用d3.js v4制作.图表主要功能是在六边形格子中显示数据,点击底部图标可以切换指定格子高亮显示,图表可以随浏览器任意缩放. 1.图表的主体结构是由正六边形组成,使用d3生成六 ...

  7. D3.js 制作中国地图

    from:  http://d3.decembercafe.org/pages/map/index.html GeoJSON is a format for encoding a variety of ...

  8. js制作简单的计算器

    学着做了一个简单的计算器!记录记录!哈哈 <!DOCTYPE html> <html> <head> <title>简单的计算器</title&g ...

  9. JS——制作简单的网页计算器

    用JS做了一个简易的网页计算器 <!DOCTYPE html> <html lang="en"> <head> <meta charset ...

随机推荐

  1. Java:控制反转(IoC)与依赖注入(DI)

    很长一段时间里,我对控制反转和依赖注入这两个概念很模糊,闭上眼睛想一想,总有一种眩晕的感觉.但为了成为一名优秀的 Java 工程师,我花了一周的时间,彻底把它们搞清楚了. 01.紧耦合 在我们编码的过 ...

  2. 3PHP如何用PDO的连接方式方式导出mysql数据

    首先连接mysql,具体看上一篇 接下来在try{}中加入以下代码 $query="select * from 你的数据表名称"          //$query的内容给个SQL ...

  3. JVM总结(二)

    JVM总结(2)java内存区域.字节码执行引擎 1.内存区域 程序计数器:知道线程执行位置,保证线程切换后能恢复到正确的执行位置. 虚拟机栈:存栈帧.栈帧里存局部变量表.操作栈.动态连接.方法返回地 ...

  4. Mysql优化-mysql分区

    背景:由于我负责i西科教务处系统,i西科用户量达到20000人左右,那么假设每人每星期10门讲课,数据库记录信息将是20万条,如果不将课程表进行分区或分表,就会造成爆表的情况,如此看来,分区是必须要做 ...

  5. Flutter学习笔记(17)--顶部导航TabBar、TabBarView、DefaultTabController

    如需转载,请注明出处:Flutter学习笔记(17)--顶部导航TabBar.TabBarView.DefaultTabController 上一篇我们说了BottmNavigationBar底部导航 ...

  6. 常见ASP脚本攻击及防范技巧

    由于ASP的方便易用,越来越多的网站后台程序都使用ASP脚本语言.但是, 由于ASP本身存在一些安全漏洞,稍不小心就会给黑客提供可乘之机.事实上,安全不仅是网管的事,编程人员也必须在某些安全细节上注意 ...

  7. 【Node/JavaScript】论一个低配版Web实时通信库是如何实现的( WebSocket篇)

    引论 simple-socket是我写的一个"低配版"的Web实时通信工具(相对于Socket.io),在参考了相关源码和资料的基础上,实现了前后端实时互通的基本功能 选用了Web ...

  8. shiro@RequiresPermission的设置

    public class MyShiroRealm extends AuthorizingRealm { //slf4j记录日志,可以不使用 private Logger logger = Logge ...

  9. Istio 太复杂?KubeSphere基于Ingress-Nginx实现灰度发布

    在 Bookinfo 微服务的灰度发布示例 中,KubeSphere 基于 Istio 对 Bookinfo 微服务示例应用实现了灰度发布.有用户表示自己的项目还没有上 Istio,要如何实现灰度发布 ...

  10. Integer 使用==判断127和超过128的数据的区别

    Integer封装类型字数据当超过一定长度后,若使用==来判断数否相等,那么判断的结果是false; Integer的范围是超过128就是false. 对于所有封装类而言,建议使用equals来进行判 ...