Html5原创俄罗斯方块(基于canvas)


Posted in HTML / CSS onJanuary 07, 2019

第一次写俄罗斯方块的时候已经是1年多前了,也是我刚刚学js不久。

为了加强对js的理解又加上对游戏的爱好,于是在没有参考他人的思路和代码下,自己用最基本的js代码写出了基于canvas的俄罗斯方块。

Html5原创俄罗斯方块(基于canvas)

在大三的暑假,我又用了es6的语法进行了改进,包含了class的语法糖、箭头函数等,进一步增加自己对es6的理解,代码有400+行

想要做这个小游戏,必须先熟悉H5的canvas,js对数组的处理,键盘事件监听和处理,定时器的使用等,其他的就是基本的逻辑处理了。

游戏的规则就是核心,也是我们代码的重中之重

这里的逻辑核心是需要判断方块是否碰撞(当前运动的方块和已经定位好的方块有碰撞以致于当前的运动的方块不能在向下走,因为我们的方块默认是向下走的,如果不能向下走,是视为已经定位好的方块,然后在生成一个新的方块从初始位置继续往下走)。

而且这个碰撞还需要应用在方块变形的时候,同样地,如果方块在变形的过程中和其他定位好的方块进行碰撞,则我们应该阻止这个方块进行变形成功,

附上代码,欢迎讨论和指正

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>es6-重构俄罗斯方块(基于canvas)</title>
    <style type="text/css">
        #tetris{ margin: 10px 250px;}
    </style>
</head>
<body>
    <canvas width="700" height="525" id="tetris"></canvas>
    <div id="text" style='color: red;font-size: 30px;'>当前分数:0</div>
    <script type="text/javascript">
        /**
         * [一个完整的俄罗斯方块类 design by magic_xiang]
         * @param  {number} side     [每个方块边长(px),默认35]
         * @param  {number} width     [一行包含的方块数(个),默认20]
         * @param  {number} height     [一列包含的方块数(个),默认15]
         * @param  {number} speed     [方块下落移动速度(ms),默认400]
         */
        class tetris{
            constructor(side=35, width=20, height=15, speed=400){
                this.side = side            // 每个方块边长
                this.width = width            // 一行包含的方块数
                this.height = height        // 一列包含的方块数
                this.speed = speed             // 方块下落移动速度
                this.num_blcok                // 当前方块类型的数字变量
                this.type_color                // 当前颜色类型的字符串变量
                this.ident                    // setInterval的标识
                this.direction = 1            // 方块方向,初始化为1,默认状态    
                this.grade = 0                // 用来计算分数
                this.over = false            // 游戏是否结束
                this.arr_bX = []            // 存放当前方块的X坐标
                this.arr_bY = []            // 存放当前方块的Y坐标
                this.arr_store_X = []        // 存放到达底部所有方块的X坐标
                this.arr_store_Y = []        // 存放到达底部所有方块的Y坐标
                this.arr_store_color = []    // 存放到达底部所有方块的颜色
                this.paints = document.getElementById('tetris').getContext('2d')
                //获取画笔
                self = this
            }

            // 封装paints方法,让代码更简洁
            paintfr(x, y, scale=1){
                this.paints.fillRect(x*this.side, y*this.side, scale*this.side, scale*this.side)
            }

            // 游戏开始
            gameStart(){
                this.init()
                this.run()
            }

            // 初始化工作
            init(){
                this.initBackground()
                this.initBlock()
            }

            // 方块自动下落
            run(){
                this.ident = setInterval("self.down_speed_up()", this.speed)
            }

            // 初始化地图
            initBackground(){
                this.paints.beginPath()
                this.paints.fillStyle='#000000'        //地图填充颜色为黑色
                for(let i = 0; i < this.height; i++){
                    for(let j = 0; j < this.width; j++){
                        this.paintfr(j, i)
                    }
                }
                this.paints.closePath()
            }

            // 初始化方块的位置和颜色
            initBlock(){
                this.paints.beginPath()
                this.createRandom('rColor')        //生成颜色字符串,
                this.paints.fillStyle = this.type_color
                this.createRandom('rBlock')        //生成方块类型数字
                this.arr_bX.forEach((item, index) => {
                    this.paintfr(item, this.arr_bY[index], 0.9)
                })
                this.paints.closePath()
            }

            // 利用数组画方块
            drawBlock(color){
                this.paints.beginPath()
                this.paints.fillStyle = color
                this.arr_bX.forEach((item, index) => {
                    this.paintfr(item, this.arr_bY[index], 0.9)
                })
                this.paints.closePath()
            }

            // 画已经在定位好的方块
            drawStaticBlock(){
                this.arr_store_X.forEach((item, index) => {
                    this.paints.beginPath()
                    this.paints.fillStyle = this.arr_store_color[index]
                    this.paintfr(item, this.arr_store_Y[index], 0.9)
                    this.paints.closePath()
                })
            }

            // 生成随机数返回方块类型或颜色类型
            createRandom(type){
                let temp = this.width/2-1

                if (type == 'rBlock'){         //如果是方块类型
                    this.num_blcok = Math.round(Math.random()*4+1)
                    switch(this.num_blcok){
                        case 1:
                            this.arr_bX.push(temp,temp-1,temp,temp+1)
                            this.arr_bY.push(0,1,1,1)
                            break
                        case 2:
                            this.arr_bX.push(temp,temp-1,temp-1,temp+1)
                            this.arr_bY.push(1,0,1,1)
                            break
                        case 3:
                            this.arr_bX.push(temp,temp-1,temp+1,temp+2)
                            this.arr_bY.push(0,0,0,0)
                            break
                        case 4:
                            this.arr_bX.push(temp,temp-1,temp,temp+1)
                            this.arr_bY.push(0,0,1,1)
                            break
                        case 5:
                            this.arr_bX.push(temp,temp+1,temp,temp+1)
                            this.arr_bY.push(0,0,1,1)
                            break
                    }
                }
                if (type == 'rColor'){                         //如果是颜色类型
                    let num_color = Math.round(Math.random()*8+1) 

                    switch(num_color){
                        case 1:
                            this.type_color='#3EF72A'
                            break
                        case 2:
                            this.type_color='yellow'
                            break
                        case 3:
                            this.type_color='#2FE0BF'
                            break
                        case 4:
                            this.type_color='red'
                            break
                        case 5:
                            this.type_color='gray'
                            break
                        case 6:
                            this.type_color='#C932C6'
                            break
                        case 7:
                            this.type_color= '#FC751B'
                            break
                        case 8:
                            this.type_color= '#6E6EDD'
                            break
                        case 9:
                            this.type_color= '#F4E9E1'
                            break
                    }
                }
            }

            // 判断方块之间是否碰撞(下),以及变形时是否越过下边界
            judgeCollision_down(){
                for(let i = 0; i < this.arr_bX.length; i++){
                    if (this.arr_bY[i] + 1 == this.height){ //变形时是否越过下边界
                        return false
                    } 
                    if (this.arr_store_X.length != 0) {    //判断方块之间是否碰撞(下)
                        for(let j = 0; j < this.arr_store_X.length; j++){
                            if (this.arr_bX[i] == this.arr_store_X[j]) {
                                if (this.arr_bY[i] + 1 == this.arr_store_Y[j]) {
                                    return false
                                }
                            }
                            
                        }
                    }    
                }
                return true
            }

            //判断方块之间是否碰撞(左右),以及变形时是否越过左右边界
            judgeCollision_other(num){
                for(let i = 0; i < this.arr_bX.length; i++){
                    if (num == 1) {            //变形时是否越过右边界
                        if (this.arr_bX[i] == this.width - 1) 
                            return false
                    }
                    if (num == -1) {                //变形时是否越过左边界
                        if (this.arr_bX[i] == 0)
                            return false
                    }
                    if (this.arr_store_X.length != 0) {                    //判断方块之间是否碰撞(左右)
                        for(let j = 0; j < this.arr_store_X.length; j++){
                            if (this.arr_bY[i] == this.arr_store_Y[j]) {
                                if (this.arr_bX[i] + num == this.arr_store_X[j]) {
                                    return false
                                }
                            }
                        }
                    }
                }
                return true;
            }


            //方向键为下的加速函数
            down_speed_up(){
                let flag_all_down = true
                flag_all_down = this.judgeCollision_down()
                
                if (flag_all_down) {
                    this.initBackground()
                    for(let i = 0; i < this.arr_bY.length; i++){
                        this.arr_bY[i] = this.arr_bY[i] + 1
                    }
                }
                else{
                    for(let i=0; i < this.arr_bX.length; i++){
                        this.arr_store_X.push(this.arr_bX[i])
                        this.arr_store_Y.push(this.arr_bY[i])
                        this.arr_store_color.push(this.type_color)
                    }
                
                    this.arr_bX.splice(0,this.arr_bX.length)
                    this.arr_bY.splice(0,this.arr_bY.length)
                    this.initBlock()
                }
                this.clearUnderBlock()
                this.drawBlock(this.type_color)
                this.drawStaticBlock()
                this.gameover()
            }

            //方向键为左右的左移动函数
            move(dir_temp){
                this.initBackground()

                if (dir_temp == 1) {                    //右
                    let flag_all_right = true
                    flag_all_right = this.judgeCollision_other(1)
                    
                    if (flag_all_right) {
                        for(let i = 0; i < this.arr_bY.length; i++){
                            this.arr_bX[i] = this.arr_bX[i]+1
                        }
                    }
                }
                else{
                    let flag_all_left = true
                    flag_all_left = this.judgeCollision_other(-1)

                    if (flag_all_left) {
                        for(let i=0; i < this.arr_bY.length; i++){
                            this.arr_bX[i] = this.arr_bX[i]-1
                        }
                    }
                }
                this.drawBlock(this.type_color)
                this.drawStaticBlock()
            }

            //方向键为空格的变换方向函数
            up_change_direction(num_blcok){ 
                if (num_blcok == 5) {
                    return
                }
                
                let arr_tempX = []
                let arr_tempY = []
                //因为不知道是否能够变形成功,所以先存储起来
                for(let i = 0;i < this.arr_bX.length; i++){    
                    arr_tempX.push(this.arr_bX[i])
                    arr_tempY.push(this.arr_bY[i])
                }
                this.direction++
                //将中心坐标提取出来,变形都以当前中心为准
                let ax_temp = this.arr_bX[0]    
                let ay_temp = this.arr_bY[0]
                
                this.arr_bX.splice(0, this.arr_bX.length)            //将数组清空 
                this.arr_bY.splice(0, this.arr_bY.length)

                if (num_blcok == 1) {
                    
                    switch(this.direction%4){
                        case 1:
                            this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp+1,ay_temp+1,ay_temp+1)
                            break
                        case 2:
                            this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp)
                            this.arr_bY.push(ay_temp,ay_temp,ay_temp-1,ay_temp+1)
                            break
                        case 3:
                            this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp,ay_temp+1,ay_temp)
                            break
                        case 0:
                            this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp)
                            break
                    }
                }
                if (num_blcok == 2) {
                    
                    switch(this.direction%4){
                        case 1:
                            this.arr_bX.push(ax_temp,ax_temp-1,ax_temp-1,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp,ay_temp-1,ay_temp)
                            break
                        case 2:
                            this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp-1)
                            this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+1)
                            break
                        case 3:
                            this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp+1)
                            break
                        case 0:
                            this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp-1)
                            break
                    }
                }
                if (num_blcok == 3) {
                    
                    switch(this.direction%4){
                        case 1:
                            this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+2)
                            this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp)
                            break
                        case 2:
                            this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp)
                            this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+2)
                            break
                        case 3:
                            this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+2)
                            this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp)
                            break
                        case 0:
                            this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp)
                            this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+2)
                            break
                    }
                }
                if (num_blcok == 4) {
                    
                    switch(this.direction%4){
                        case 1:
                            this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp,ay_temp+1,ay_temp+1)
                            break
                        case 2:
                            this.arr_bX.push(ax_temp,ax_temp,ax_temp+1,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp+1,ay_temp,ay_temp-1)
                            break
                        case 3:
                            this.arr_bX.push(ax_temp,ax_temp,ax_temp-1,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp-1,ay_temp,ay_temp-1)
                            break
                        case 0:
                            this.arr_bX.push(ax_temp,ax_temp,ax_temp+1,ax_temp+1)
                            this.arr_bY.push(ay_temp,ay_temp-1,ay_temp,ay_temp+1)
                            break
                    }
                }
                
                if (! (this.judgeCollision_other(-1) && this.judgeCollision_down() && this.judgeCollision_other(1)  )) {            //如果变形不成功则执行下面代码
                    this.arr_bX.splice(0, this.arr_bX.length)             
                    this.arr_bY.splice(0, this.arr_bY.length)
                    for(let i=0; i< arr_tempX.length; i++){
                        this.arr_bX.push(arr_tempX[i])
                        this.arr_bY.push(arr_tempY[i])
                    }
                }
                this.drawStaticBlock()
            }

            //一行满了清空方块,上面方块Y坐标+1
            clearUnderBlock(){
                //删除低层方块
                let arr_row=[]
                let line_num
                if (this.arr_store_X.length != 0) {
                    for(let j = this.height-1; j >= 0; j--){
                        for(let i = 0; i < this.arr_store_color.length; i++){
                            if (this.arr_store_Y[i] == j) {
                                arr_row.push(i)
                            }
                        }
                        if (arr_row.length == this.width) {
                            line_num = j
                            break
                        }else{
                            arr_row.splice(0, arr_row.length)
                        }
                    }
                }
                    
                if (arr_row.length == this.width) {
                    //计算成绩grade
                    this.grade++
                    
                    document.getElementById('text').innerHTML = '当前成绩:'+this.grade
                    for(let i = 0; i < arr_row.length; i++){
                        this.arr_store_X.splice(arr_row[i]-i, 1)
                        this.arr_store_Y.splice(arr_row[i]-i, 1)
                        this.arr_store_color.splice(arr_row[i]-i, 1)
                    }
                
                    //让上面的方块往下掉一格
                    for(let i = 0; i < this.arr_store_color.length; i++){
                        if (this.arr_store_Y[i] < line_num) {
                            this.arr_store_Y[i] = this.arr_store_Y[i]+1
                        }
                    }
                }
            }

            //判断游戏结束
            gameover(){
                for(let i=0; i < this.arr_store_X.length; i++){
                    if (this.arr_store_Y[i] == 0) {
                        clearInterval(this.ident)
                        this.over = true
                    }
                }
            }
        }

        let tetrisObj = new tetris()
        tetrisObj.gameStart()
        
        //方向键功能函数
        document.onkeydown = (e) => {   
            if (tetrisObj.over)
                return

            switch(e.keyCode){
                case 40:  // 方向为下
                    tetrisObj.down_speed_up()
                    break
                case 32:  // 空格换方向
                    tetrisObj.initBackground()        //重画地图
                    tetrisObj.up_change_direction(tetrisObj.num_blcok)
                    tetrisObj.drawBlock(tetrisObj.type_color)
                    break
                case 37:  // 方向为左
                    tetrisObj.initBackground()
                    tetrisObj.move(-1)
                    tetrisObj.drawBlock(tetrisObj.type_color)
                    break
                case 39:  // 方向为右
                    tetrisObj.initBackground()
                    tetrisObj.move(1)
                    tetrisObj.drawBlock(tetrisObj.type_color)
                    break
            }    
        }

    </script>
</body>
</html>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

HTML / CSS 相关文章推荐
CSS3字体效果的设置方法小结
Jun 13 HTML / CSS
一款纯css3实现的漂亮的404页面的实例教程
Nov 27 HTML / CSS
Css3新特性应用之形状总结
Dec 08 HTML / CSS
html5嵌入内容_动力节点Java学院整理
Jul 07 HTML / CSS
html5 button autofocus 属性介绍及应用
Jan 04 HTML / CSS
HTML5使用drawImage()方法绘制图像
Jun 23 HTML / CSS
实例教程 HTML5 Canvas 超炫酷烟花绽放动画实现代码
Nov 05 HTML / CSS
带你认识HTML5中的WebSocket
May 22 HTML / CSS
使用纯HTML5编写一款网页上的时钟的代码分享
Nov 16 HTML / CSS
浅谈Html5移动端ios/Android兼容性总结
Jun 01 HTML / CSS
HTML5实现视频弹幕功能
Aug 09 HTML / CSS
CSS实现鼠标悬浮动画特效
May 07 HTML / CSS
h5使用canvas画布实现手势解锁
Jan 04 #HTML / CSS
canvas中普通动效与粒子动效的实现代码示例
Jan 03 #HTML / CSS
详解webapp页面滚动卡顿的解决办法
Dec 26 #HTML / CSS
Html5调用手机摄像头并实现人脸识别的实现
Dec 21 #HTML / CSS
HTML5的postMessage的使用手册
Dec 19 #HTML / CSS
使用html2canvas.js实现页面截图并显示或上传的示例代码
Dec 18 #HTML / CSS
Canvas globalCompositeOperation
Dec 18 #HTML / CSS
You might like
IP138 IP地址查询小偷实现代码
2010/02/15 PHP
php通过文件头检测文件类型通用代码类(zip,rar等)
2010/10/19 PHP
PHP实现的大文件切割与合并功能示例
2018/04/10 PHP
正则表达式搭配js轻松处理json文本方便而老古
2013/02/17 Javascript
jquery实现图片翻页效果
2013/12/23 Javascript
浅析JavaScript基本类型与引用类型
2014/05/28 Javascript
基于BootStarp的Dailog
2016/04/28 Javascript
EasyUI学习之Combobox级联下拉列表(2)
2016/12/29 Javascript
快速掌握jquery分页插件jqPaginator的使用方法
2017/08/09 jQuery
微信小程序 动画的简单实例
2017/10/12 Javascript
浅谈jquery fullpage 插件增加头部和版权的方法
2018/03/20 jQuery
JavaScript实现多态和继承的封装操作示例
2018/08/20 Javascript
vue自定义键盘信息、监听数据变化的方法示例【基于vm.$watch】
2019/03/16 Javascript
Vue将页面导出为图片或者PDF
2020/08/17 Javascript
JavaScript之Blob对象类型的具体使用方法
2019/11/29 Javascript
如何基于javascript实现贪吃蛇游戏
2020/02/09 Javascript
[02:53]DOTA2亚洲邀请赛 NewBee战队巡礼
2015/02/03 DOTA
[53:15]Newbee vs Pain 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
[06:07]DOTA2-DPC中国联赛 正赛 Ehome vs VG 选手采访
2021/03/11 DOTA
Python基于scrapy采集数据时使用代理服务器的方法
2015/04/16 Python
Python和C/C++交互的几种方法总结
2017/05/11 Python
Python中的defaultdict与__missing__()使用介绍
2018/02/03 Python
python MySQLdb使用教程详解
2018/03/20 Python
python生成特定分布数的实例
2019/12/05 Python
pytorch中获取模型input/output shape实例
2019/12/30 Python
python十进制转二进制的详解
2020/02/07 Python
CSS3毛玻璃效果(blur)有白边问题的解决方法
2016/11/15 HTML / CSS
Silk’n激光脱毛器官网:silkn.com
2016/10/06 全球购物
联想瑞士官方网站:Lenovo Switzerland
2017/11/19 全球购物
什么是SQL Server的确定性函数和不确定性函数
2016/08/04 面试题
历史学专业求职信
2014/06/19 职场文书
教师节感谢信
2015/01/22 职场文书
2015年百日安全活动总结
2015/03/26 职场文书
详细聊聊MySQL中慢SQL优化的方向
2021/08/30 MySQL
python自动化测试之Selenium详解
2022/03/13 Python
springboot用户数据修改的详细实现
2022/04/06 Java/Android