Funpack第十期用Kitronik ARCADE玩游戏——俄罗斯方块
Funpack 第十期 Kitronik ARCADE 游戏 JavaScript 俄罗斯方块
标签
测试
aramy
更新2021-08-26
711

内容介绍

 

   硬件介绍:funpack第十期带来的Kitronik ARCADE是一款功能丰富的可编程游戏手柄,搭配微软MakeCode Arcade编辑器编程使用。处理器 Atmel SAMD51J19A,工作电压 3节5号电池(3.6-4.5V)或USB(通常为5V)。经测试在3.6v的外接电源也能正常工作,也就是说这个游戏机可以直接改为锂电池供电。

    任务:这里选择的是任务一,在这个游戏手柄上实现经典游戏——俄罗斯方块。

    实现过程
    1、选择任务。拿到开发板后,非常开心,板子很漂亮。两层透明亚克力外壳,非常适合手握。PCB貌似是2mm的板子,强度很不错,按键手感也很好。按funpack的页面介绍,了解了一些微软的makecode编程,我的理解makecode基本是一个图形化编程方式,感觉面向青少年的,自己不是太喜欢,总感觉很多逻辑用图形方式展示不清楚。任务有两个,想跳过makecode直接对芯片编程,实现莫斯电码。花了一段时间,找了些资料,又看了直播课,感觉直接芯片编程应该是不可行。于是就切换任务,来实现任务一,来实现玩俄罗斯方块。选定了任务,上网寻找相关资源,发现关于makecode很多教程,都是教青少年在这个硬件上做游戏的,按着教程入门了一下,发现一个问题,几乎所有的教程教的游戏都是关于角色扮演类的(+﹏+) ,俄罗斯方块类型的游戏资料很少!但是既然选了这个任务,就一定要完成!
    2、游戏介绍。俄罗斯方块可是个经典老游戏啦!FtQ_lrHDXMID0PzfiWfY_Bhoj0x5通过不停第掉落不同形状的方块,用手柄控制姿态,然后摆放,当一行摆满了就消除一行,直到无法摆放则游戏失败。俄罗斯方块有很多变种,有更加复杂的方块玩法,这里做了简化,仅仅实现最简单的游戏。掉落的方块有7种类型。条状(最让人喜欢的,能救命的,由四个方块组成的直线)、L状(4个方块组成,带尾巴),L状的镜像、正方形(4个小方块组成的大方块)、T型(中间凸起的)、Z型及其镜像(最烦人的形状)。屏幕每行为19个方块,高度为18个方块。FudXK9nSvGTxGg9K8BvgvQpWAPAN
    3、实现。通过直播课和网上的资源,慢慢地觉得图形化编程真心不合适做俄罗斯方块这样类型的游戏。好在微软还提供了javascript和python的方式编程。但是提供的python很奇怪,感觉和自己了解的python不太一样,而且感觉对python支持也不是太好。在实现过程中发现有时javascript的代码能够通过,但是转到图形界面就会出错,原因暂时未知。所以这里就使用了单纯的javascript编程。Fva5A591waeqGugwrRRWxxe_oFye
    首先定义7种不同的方块,每种方块有4种变形,对应着4个方向的旋转。这里每个图形都用数组矩阵常量来定义,每种图形用一个字母来标识

const I = [
    [
        [0, 0, 0, 0],
        [1, 1, 1, 1],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
    ],
    [
        [0, 0, 1, 0],
        [0, 0, 1, 0],
        [0, 0, 1, 0],
        [0, 0, 1, 0],
    ],
    [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [1, 1, 1, 1],
        [0, 0, 0, 0],
    ],
    [
        [0, 1, 0, 0],
        [0, 1, 0, 0],
        [0, 1, 0, 0],
        [0, 1, 0, 0],
    ]
]
const J = [
    [
        [1, 0, 0],
        [1, 1, 1],
        [0, 0, 0]
    ],
    [
        [0, 1, 1],
        [0, 1, 0],
        [0, 1, 0]
    ],
    [
        [0, 0, 0],
        [1, 1, 1],
        [0, 0, 1]
    ],
    [
        [0, 1, 0],
        [0, 1, 0],
        [1, 1, 0]
    ]
]
const L = [
    [
        [0, 0, 1],
        [1, 1, 1],
        [0, 0, 0]
    ],
    [
        [0, 1, 0],
        [0, 1, 0],
        [0, 1, 1]
    ],
    [
        [0, 0, 0],
        [1, 1, 1],
        [1, 0, 0]
    ],
    [
        [1, 1, 0],
        [0, 1, 0],
        [0, 1, 0]
    ]
]
const O = [
    [
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0],
    ],
    [
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0],
    ],
    [
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0],
    ],
    [
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0],
    ]
]
const S = [
    [
        [0, 1, 1],
        [1, 1, 0],
        [0, 0, 0]
    ],
    [
        [0, 1, 0],
        [0, 1, 1],
        [0, 0, 1]
    ],
    [
        [0, 0, 0],
        [0, 1, 1],
        [1, 1, 0]
    ],
    [
        [1, 0, 0],
        [1, 1, 0],
        [0, 1, 0]
    ]
]
const T = [
    [
        [0, 1, 0],
        [1, 1, 1],
        [0, 0, 0]
    ],
    [
        [0, 1, 0],
        [0, 1, 1],
        [0, 1, 0]
    ],
    [
        [0, 0, 0],
        [1, 1, 1],
        [0, 1, 0]
    ],
    [
        [0, 1, 0],
        [1, 1, 0],
        [0, 1, 0]
    ]
]
const Z = [
    [
        [1, 1, 0],
        [0, 1, 1],
        [0, 0, 0]
    ],
    [
        [0, 0, 1],
        [0, 1, 1],
        [0, 1, 0]
    ],
    [
        [0, 0, 0],
        [1, 1, 0],
        [0, 1, 1]
    ],
    [
        [0, 1, 0],
        [1, 1, 0],
        [1, 0, 0]
    ]
]

每个方块在运动中,遇到边界或其它方块则阻挡时,无法旋转。这个逻辑单独做出一个函数,这里也是无法使用图形化编程的主要原因,这个函数的入口参数有个数组,在图形界面中总是不行。

function collision(piece: number[][], x: number, y: number) {
    for (let row = 0; row <= piece.length - 1; row++) {
        for (let col = 0; col <= piece[row].length - 1; col++) {

            if (piece[row][col] == 0) {continue}

            if (pieceY < 0) {continue}

            if ((pieceX + col + x < 0) || (pieceX + col + x > COL) || (pieceY + row + y > ROW)) 
            {
                music.playTone(Note.F, BeatFraction.Half)
                return true
            }
            
            if (grid[pieceY + row + y][pieceX + col + x] != VACANT) 
            {
                music.playTone(Note.E5, BeatFraction.Half)
                return true
            }
        }
    }
    return false
}

主要的处理事件方法是定时处理,这里选用每500毫秒调用一次。如果觉得难度简单,可以调小这个函数game.onUpdateInterval的第一个入口参数,单位是毫秒。
这里的处理逻辑是先调用collision函数判断是否能移动,不能移动 判断是否堆满了,如果堆满了,当然是游戏结束(失败)。没堆满的话,检查是否一行方块凑齐了,凑齐了的话消减一行并播放音乐,产生新的块。能移动的话,就向下移动。

game.onUpdateInterval(500, function () {
    if (collision(currPiece[pieceRot], 0, 1)) {
        if (pieceY > 0) {
            lockPiece(currPiece[pieceRot])
            checkLine()
            newPiece()
        }else{
            game.over(false)
        }
    } else {
        drawPiece(currPiece[pieceRot], pieceX, pieceY, VACANT)
        pieceY++
        drawPiece(currPiece[pieceRot], pieceX, pieceY, pieceColor)
    }
})

按键处理事件。左右键按下事件,对应当前方块向左或向右移动。下键,对应两个触发事件,一个是下键按下事件,当前方块向下移动一格;下键被重复按动了,当前方块加速下移。A键按下,对应顺时针旋转90度事件,B键对应逆时针旋转90度事件。

// Controls
controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
    let newRot: number
    if (pieceRot != 3) {
        newRot = pieceRot + 1
    } else {
        newRot = 0
    }
    if (collision(currPiece[newRot], 0, 0)) {

    } else {        
        drawPiece(currPiece[pieceRot], pieceX, pieceY, VACANT)
        pieceRot = newRot
        drawPiece(currPiece[pieceRot], pieceX, pieceY, pieceColor)
    }
})
controller.B.onEvent(ControllerButtonEvent.Pressed, function () {
    let newRot: number
    if (pieceRot != 0) {
        newRot = pieceRot - 1
    } else {
        newRot = 3
    }
    if (collision(currPiece[newRot], 0, 0)) {

    } else {        
        drawPiece(currPiece[pieceRot], pieceX, pieceY, VACANT)
        pieceRot = newRot
        drawPiece(currPiece[pieceRot], pieceX, pieceY, pieceColor)
    }
})
controller.down.onEvent(ControllerButtonEvent.Pressed, function () {
    if (collision(currPiece[pieceRot], 0, 1)) {

    } else {
        drawPiece(currPiece[pieceRot], pieceX, pieceY, VACANT)
        pieceY++
        drawPiece(currPiece[pieceRot], pieceX, pieceY, pieceColor)
    }
})
controller.down.onEvent(ControllerButtonEvent.Repeated, function () {
    if (collision(currPiece[pieceRot], 0, 1)) {

    } else {
        drawPiece(currPiece[pieceRot], pieceX, pieceY, VACANT)
        pieceY++
        drawPiece(currPiece[pieceRot], pieceX, pieceY, pieceColor)
    }
})
controller.left.onEvent(ControllerButtonEvent.Pressed, function () {
    if (collision(currPiece[pieceRot], -1, 0)) {

    } else {
        drawPiece(currPiece[pieceRot], pieceX, pieceY, VACANT)
        pieceX--
        drawPiece(currPiece[pieceRot], pieceX, pieceY, pieceColor)
    }
})
controller.right.onEvent(ControllerButtonEvent.Pressed, function () {
    if (collision(currPiece[pieceRot], 1, 0)) {

    } else {
        drawPiece(currPiece[pieceRot], pieceX, pieceY, VACANT)
        pieceX++
        drawPiece(currPiece[pieceRot], pieceX, pieceY, pieceColor)
    }
})

    完成的程序:俄罗斯方块FjnklJv9eG0wUF8HqFN9b3eevp3lFucY0Xm-v-FsvrR8bIbStdZMsSzq

    遇到的问题:1 想在game.onUpdateInterval函数中将速度做为变量,当消掉一行后提高速度,但是不起作用。该函数应该是没有重入 2 程序启动后屏幕颜色会变换,不知道为什么,多次reset后就正常了。3 感觉在实体机上不如在模拟器上灵活,有时感觉按键响应不是太灵敏,重复按下键后,有时会突然加速多次。

    心得体会:感谢funpack带来的这期活动。见识了makecode图形化编程。才发现面对青少年编程教育也是进行的如火如荼,将枯燥的编程,变得有趣着实不易,同时拓宽了视野,同样的问题,不同角度去解决,总有意想不到的惊喜。

软件 & 硬件

电路图

附件下载

arcade-俄罗斯方块.uf2

团队介绍

团队成员
aramy
单片机业余爱好者,瞎捣鼓小能手。

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号