d3.js是一个不错的可视化框架,同时对于操作dom也是十分方便的。今天我们使用d3.js配合es6的类来制作一个童年小游戏–贪吃蛇。话不多说先上图片。
1. js snaker类
class Snaker { constructor() { this._size = 30; this._len = 3; this._width = 900; this._height = 690; this._rows = 23; this._cols = 30; this._colors = d3.scaleLinear().range(['#E75229','#FFBF35']); this._svg = null; this._currentArray = [[0,2],[0,1],[0,0]]; this._interval = null; this._duration = 1000; this._direction = 1;//上右下左0123 this._randomPosition = [0,6]; this.initSvg(); this.addKeyListener(); } initSvg() { this._svg = d3.select('.svg-container') .append('svg') .attr('width', this._width) .attr('height', this._height) this._svg.selectAll('line.rows') .data(d3.range(this._rows)) .enter() .append('line') .attr('class', 'line rows') .attr('x1', 0) .attr('y1', d => d * this._size) .attr('x2', this._width) .attr('y2', d => d * this._size) this._svg.selectAll('line.cols') .data(d3.range(this._cols)) .enter() .append('line') .attr('class', 'line cols') .attr('x1', d => d * this._size) .attr('y1', 0) .attr('x2', d => d * this._size) .attr('y2', this._height) } addKeyListener() { d3.select('body').on('keydown', () => { switch (d3.event.keyCode) { case 37: this.rotate(3); break; case 38: this.rotate(0); break; case 39: this.rotate(1); break; case 40: this.rotate(2); break; case 32: console.log('空格'); break; case 80: console.log('暂停'); break; default: break; } }) } rotate(num) { if(num == this._direction) { this.rotateMove(); } else if(num % 2 != this._direction % 2) { this._direction = num; this.rotateMove(); } } renderSnaker() { this._svg.selectAll('rect.active').remove(); this._svg.selectAll('rect.active') .data(this._currentArray) .enter() .append('rect') .attr('class', 'active') .attr('x', d => d[1] * this._size) .attr('y', d => d[0] * this._size) .attr('width', this._size) .attr('height', this._size) .attr('fill', (d,i) => this._colors(i / this._len)) .attr('stroke', (d,i) => this._colors(i / this._len)) } canMove() { //下一步没有触碰边缘 let noTouchBorder = true; //下一步没有触碰自身 let noTouchSelf = true; //新数组 let newArray = []; //判断方向 switch(this._direction) { case 0: if(this._currentArray[0][0] == 0) { noTouchBorder = false; } else { newArray = this._currentArray.map((c,i,arr) => { if(i == 0) { return [c[0] - 1, c[1]] } else { return arr[i - 1] } }) } break; case 1: if(this._currentArray[0][1] == this._cols - 1) { noTouchBorder = false; } else { newArray = this._currentArray.map((c,i,arr) => { if(i == 0) { return [c[0], c[1] + 1] } else { return arr[i - 1] } }) } break; case 2: if(this._currentArray[0][0] == this._rows - 1) { noTouchBorder = false; } else { newArray = this._currentArray.map((c,i,arr) => { if(i == 0) { return [c[0] + 1, c[1]] } else { return arr[i - 1] } }) } break; case 3: if(this._currentArray[0][1] == 0) { noTouchBorder = false; } else { newArray = this._currentArray.map((c,i,arr) => { if(i == 0) { return [c[0], c[1] - 1] } else { return arr[i - 1] } }) } break; } //判断新数组第一个元素是否出现在后面其他元素中 for(var i=1; i) { if(newArray[0][0] == newArray[i][0] && newArray[0][1] == newArray[i][1]) { noTouchSelf = false; } } return noTouchBorder && noTouchSelf; } setScoreAndSpeed() { d3.select('#score').html(this._len); d3.select('#speed').html((this._duration * (1 - this._len / 1000) / 1000).toString().substr(0,8) + 's') } moveArray() { if(this.canMove()) { if(this._direction == 0) { if(this._currentArray[0][0] - 1 == this._randomPosition[0] && this._currentArray[0][1] == this._randomPosition[1]) { this._currentArray.unshift(this._randomPosition); this._len ++; this.setScoreAndSpeed(); this.removeRandomPosition(); this.randomPosition(); } else { this._currentArray.unshift([this._currentArray[0][0] - 1,this._currentArray[0][1]]) this._currentArray.pop(); } } else if(this._direction == 1) { if(this._currentArray[0][0] == this._randomPosition[0] && this._currentArray[0][1] + 1 == this._randomPosition[1]) { this._currentArray.unshift(this._randomPosition); this._len ++; this.setScoreAndSpeed(); this.removeRandomPosition(); this.randomPosition(); } else { this._currentArray.unshift([this._currentArray[0][0],this._currentArray[0][1] + 1]) this._currentArray.pop(); } } else if(this._direction == 2) { if(this._currentArray[0][0] + 1 == this._randomPosition[0] && this._currentArray[0][1] == this._randomPosition[1]) { this._currentArray.unshift(this._randomPosition); this._len ++; this.setScoreAndSpeed(); this.removeRandomPosition(); this.randomPosition(); } else { this._currentArray.unshift([this._currentArray[0][0] + 1,this._currentArray[0][1]]) this._currentArray.pop(); } } else if(this._direction == 3) { if(this._currentArray[0][0] == this._randomPosition[0] && this._currentArray[0][1] - 1 == this._randomPosition[1]) { this._currentArray.unshift(this._randomPosition); this._len ++; this.setScoreAndSpeed(); this.removeRandomPosition(); this.randomPosition(); } else { this._currentArray.unshift([this._currentArray[0][0],this._currentArray[0][1] - 1]) this._currentArray.pop(); } } } else { console.log('game over'); alert('game over') } } removeRandomPosition() { d3.selectAll('rect.random').remove(); } randomPosition() { let random = Math.floor(Math.random() * (this._cols * this._rows - this._len)); let temp = []; for(var i=0; i<this._rows; i++) { for(var j=0; j<this._cols; j++) { temp.push([i,j]) } } let emptyArray = temp.filter(a => !this._currentArray.some(b => b[0] == a[0] && b[1] == a[1])); this._randomPosition = emptyArray[random]; this._svg.append('rect') .attr('class', 'random') .attr('x', this._randomPosition[1] * this._size) .attr('y', this._randomPosition[0] * this._size) .attr('width', this._size) .attr('height', this._size) } interval() { this._interval = setInterval(() => { this.moveArray(); this.renderSnaker(); }, this._duration * (1 - this._len / 1000)) } //转弯附带移动一次 rotateMove() { this.moveArray(); this.renderSnaker(); } initData() { this._currentArray = [[0,2],[0,1],[0,0]]; } start() { this.initData(); this.renderSnaker(); this.interval(); this.randomPosition(); this.setScoreAndSpeed(); } }
2. css 代码
* { padding: 0; margin: 0; } .container { width: 100vw; height: 100vh; } .svg-container { margin: 50px; width: 900px; height: 690px; border: 3px double #666; display: inline-block; overflow: hidden; } aside { width: 200px; height: 300px; display: inline-block; vertical-align: top; margin-top: 50px; } .line { shape-rendering: crispEdges; stroke: #bbbbbb; } .active { stroke-width: 2; fill-opacity: 0.5; } .random { fill: #ff00ff; fill-opacity: 0.5; stroke: #ff00ff; stroke-width: 2; }
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>
<aside>
<table>
<tr>
<td>当前分数:td>
<td id="score">td>
tr>
<tr>
<td>当前速度:td>
<td id="speed">td>
tr>
table>
<button onclick="start()">开始游戏button>
aside>
div>
<script>
var snaker = new Snaker();
function start() {
snaker.start();
}
script>
body>
html>
有想预览或者下载demo的朋友请移步至个人博客
原文地址 http://www.bettersmile.cn