d3.js 制作简单的贪吃蛇

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<newArray.length; 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

原文地址:https://www.cnblogs.com/vadim-web/p/11496275.html