zoukankan      html  css  js  c++  java
  • d3.js 制作简单的俄罗斯方块

    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: 0;
      margin: 0;
    }
    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: 100;
      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

  • 相关阅读:
    UVA10765图论+点-双连通分量性质应用
    LA4287图论+ 有向图SCC+缩点
    LA5135图论+ 割点性质运用
    LA2572计算几何+离散化+面的覆盖
    LA2402暴力枚举+计算几何+四边形面积
    UVA10566计算几何+相似三角形比例函数+二分范围的辨析
    UVA11300计算几何:正n边形内的最长的线
    UVA11524平面几何+二分法+海伦公式
    LA4986三分法求出凹性函数最小值+计算几何
    胜利大逃亡--hdu --1253(bfs)
  • 原文地址:https://www.cnblogs.com/vadim-web/p/11492095.html
Copyright © 2011-2022 走看看