Bootstrap

love2d 代码库(六)俄罗斯方块青春版

效果如下

在这里插入图片描述

代码简介

俄罗斯方块大家都玩过,不做过多赘叙。

代码思路

1. 图像生成

1.1 场景生成

遍历19*18二维数组,
value=0 即空
value =1 即可以移动的blcok
value =2 即已经不会动的block
block为30*30的白色方块。

此处有取巧的设置,可以设置 20 ∗ 20 20*20 2020的二维数组,但是令最外圈的value=2,因为我绘制函数只画 19 ∗ 18 19*18 1918的部分,则初始value=2的部分相当于隐形墙壁,我们可以通过检测碰撞的方法做到限制方块的移动范围。

1.2 不同组合的方块

例如L型,同样是设置二维数组,遍历来打印图形

L_tetrimino = {
	{1,0},
	{1,0},
	{1,1},
	x=0,
	y=0
}

这里的x,y 作为L型方块的坐标,方便去计算方块的移动

2.功能实现

基本功能也就是方块的一生

2.1 活动方块的诞生

function block_new()
	-- 用来旋转的
    shape_num =1 
    -- 判断方块是否货主
    Blockstate = true
    -- 初始化出生坐标
    Tetrimino.x=7
    Tetrimino.y = 0
    -- 为随机变成不同样子做准备
    local num = math.random(1, 6)
    local name = TetriminoName[num]
    X_Tetrimino=Tetrimino[name]
    return X_Tetrimino[1],X_Tetrimino
end

2.2 方块的零件一:移动功能

  1. 利用下述代码实现计时器功能,让后设置一秒降落一格
-- 设置时间为1秒
moveInterval=1
function love.update(dt)
    timeAcumulator = timeAcumulator + dt
    if timeAcumulator>=moveInterval then
        L_tetrimino.y=L_tetrimino.y+1
        --时间计数器减去1秒又回到初始的地方开始计数
        timeAcumulator = timeAcumulator - moveInterval 
        Block_drop(shape)
    end   
end
  1. 利用计时器对L型方块的坐标(x,y)进行计算,利用L型方块的行数和列数计算出L型方块在Blocklist中的位置并赋值,可这样遇到了第二个问题:方块落下的拖尾
function Block_drop(shape)
    for i =1, #shape,1 do
        for j = 1,#shape[i],1 do
            blockList[y + i][x+ j] = shape[i][j]
        end
    end
end

2.3 方块的零件二:清理旧状态

我们通过对问题:方块落下的拖尾的探究已经得到了如何去消除方块的旧状态

function clearOld()
    for i = 1, #shape, 1 do
        for j = 1, #shape[i], 1 do
            if blockList[Tetrimino.y + i][Tetrimino.x + j] == 1 then
                blockList[Tetrimino.y + i][Tetrimino.x + j] = 0
            end
            
        end
    end
end

2.34 方块的零件三:碰撞检测

检测活动方块和固定方块是否碰撞,来判断活动方块是否可以进行旋转和移动

--碰撞检测
function isCollide(x, y, t)
    for i = 1, #t, 1 do
        for j = 1, #t[i], 1 do
            if t[i][j] == 1 and blockList[y + i][x + j] == 2 then
                return true
            end
        end
    end
    return false
end

2.5 方块的零件四:更新状态

只要可以移动,我们就需要更新移动后的位置

function updateNEW(x,y)
    Tetrimino.x = x
    Tetrimino.y = y
        for i = 1, #shape, 1 do
            for j = 1, #shape[i], 1 do
                if shape[i][j]==1 then
                    blockList[y + i][x + j] = shape[i][j]
                end
            end
        end
end

2.6 方块的死亡

function block_fixed(x,y)
    for i = 1, #shape, 1 do
            for j = 1, #shape[i], 1 do
                if shape[i][j]==1  then
                    blockList[y + i-1][x + j] = 2
                end
                
            end
    end
    -- 标志一个方块的死亡
    Blockstate =false
end

遇到的问题

1️⃣ 在main文件中使用Block文件中的全局变量const时报错

<main.lua>

-- shape = L_tetrimino.shape1  此处是错误地方一,因为还没有require 库Block
function love.load()
    Object =require "Classic"
    require "Block"
    require "const"
    --block= Block()
    -- 全局变量要写在 load里,我干。可能是因为函数无序性
    -- 此处是正确的地方
    shape = L_tetrimino.shape1
end
-- shape = L_tetrimino.shape1 此处是错误地方二:因为具有无序性,所以可能还没有require Block 就使用了cons
function love.update(dt)
    shape.y=11
    Block_drop(shape)
    
    
end

function love.draw()
    
    draw_Blcok()
end 

2️⃣方块落下的拖尾

问题如图:
在这里插入图片描述

问题分析

每隔一秒从L型方块赋值给Blocklist,但是没有对上一秒的状态进行清理,导致Blocklist中的过去状态的位置仍然是1

解决方法

  1. 修改二维数组:对L型方块的二维数组进行修改,在顶部多加一行空白,这样就实现了每次下落一格时,自动清除上一格的信息。
L_tetrimino = {
	{0,0},
	{1,0},
	{1,0},
	{1,1},
	x=0,
	y=0
}
  1. 先洗碗后吃饭:既然是过去的状态信息没有清理干净,那我每次都先清理干净再去绘制图像。
function Block_drop(shape)
    local x = L_tetrimino.x
    local y = L_tetrimino.y
    --清理信息
    for i = 1, #shape, 1 do
        for j = 1, #shape[i], 1 do
            blockList[y + i-1][x + j] = 0
        end
    end
    --绘制图像
    for i =1, #shape,1 do
        for j = 1,#shape[i],1 do
            blockList[y + i][x+ j] = shape[i][j]
        end
    end
    
end

注意,清理信息和绘制图像两个不能颠倒顺序

同时这个方法在方块左移和右移的时候该如何去处理拖尾呢?

好吧,我承认前面写的有问题,但是基本思路是对的。对于处理左移右移和下移其实是一个方法,我们不过是想在画新状态之前清理旧状态信息,而新旧状态的辨别就是坐标(x,y),我们可以让local a =x ,localb =y的思路,让a和b去参加运算作为新状态,而x,y做旧状态。我们利用玩x,y清理旧信息后,直接让x=a,y=b 完成状态转换。

通过下述代码我能实现方块左移

function love.keypressed(key)

    local x = L_tetrimino.x
    local y = L_tetrimino.y
    if key == "right" then
        x =  x+1
        for i = 1, #shape, 1 do
            for j = 1, #shape[i], 1 do
                blockList[L_tetrimino.y+ i][L_tetrimino.x + j] = 0
            end
        end
        L_tetrimino.x=x
        L_tetrimino.y=y
        for i = 1, #shape, 1 do
            for j = 1, #shape[i], 1 do
                blockList[y + i][x+ j] = shape[i][j]
            end
        end
    end
end

效果如下:
在这里插入图片描述

项目代码

<main.lua>

if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
    require("lldebugger").start()
end
function love.load()
    math.randomseed(os.time())
    Object =require "Classic"
    require "Block"
    require "const"
    require "Button"
    buttonPause = Button("Pause",600,380)
    buttonPlay = Button("Play",600,460)
    -- 全局变量要写在 load里,我干。可能是因为函数无序性
    shape,new_Tetrimino = block_new()
    NextShape,next_Tetrimino = block_new()
    ---*************
    
end
function love.keypressed(key)
    if not IsPause then
        if key == "right" then
            x = x + 1
        elseif key == "left" then
            x=x-1
        elseif key == "up" then
            block_rotate(x,y)
        end
        if x < 1 then
            x = 0
        elseif x > 17 then
            x = 17
        end
        --碰撞检测
        if not isCollide(x, y, shape) then
            --清除信息
            clearOld()
            updateNEW(x, y)
        end
        if key=="down" then
            y=y+1
            if isCollide(x, y, shape) then
                --清除信息
                block_fixed(x,y)
            else
                clearOld()
                updateNEW(x, y)
            end
        end
    end
end
function love.mousepressed(x,y,button1,istouch,presses)
    if button1==1 and buttonPause:isMouseHover() then
        IsPause = true
    elseif  button1==1 and buttonPlay:isMouseHover() then
        IsPause = false
    end

end
function love.update(dt)
    x = Tetrimino.x
    y = Tetrimino.y
    if not IsPause then
        if not Blockstate then
            shape = NextShape
            
            new_Tetrimino = next_Tetrimino
            NextShape,next_Tetrimino = block_new()
        end
        block_remove()
        timeAcumulator = timeAcumulator + dt
        if timeAcumulator>=moveInterval then
            y=y+1
            timeAcumulator = timeAcumulator - moveInterval
            block_drop(x, y)
        end
    end
end

function love.draw()
    
    love.graphics.setLineWidth(5)
    love.graphics.line(562, 0, 562, 600)
    -- 预览框
    love.graphics.rectangle("line",600,50,180,180)
    --按钮
   buttonPause:draw()
   buttonPlay:draw()
    --分数
    love.graphics.print("Playersocre:"..Playersocre,600,250)
    Preblockdraw()
    draw_Blcok()
end 

<const.lua>


timeAcumulator = 0
moveInterval = 1
backColor = "#ffffff"
Playersocre = 0 
IsPause = false
-- 其实板子可以更大,我画的时候选取一部分画就行
--19*18
blockList = {
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2},
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 },
    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 },
}
Removerow = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }
newrow = { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
TetriminoName = {"L_tetrimino" ,"O_tetrimino" ,"I_tetrimino","T_tetrimino","Z_tetrimino","N_tetrimino" }
--shape = 4*4
Tetrimino=
{
    L_tetrimino = 
    {
        {
            { 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}
        },
        {
            { 0,  0, 0 },
            { 0 ,0, 1 },
            { 1, 1, 1 }
        }
    },
    O_tetrimino = 
    {
        {
            { 0,  0, 0 },
            { 1 ,1, 0 },
            { 1, 1, 0}
        }
    },
    I_tetrimino = 
    {
        
        {
            { 0,1, 0},
            { 0,1, 0},
            { 0,1, 0},
            { 0,1, 0}
        },
        {
            { 0,  0, 0 },
            { 1, 1 ,1,1}
        }
    },
    T_tetrimino=

    {
        {
            { 0,1, 0},
            { 1,1, 1} 
        },
        {
            { 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} 
        }
    },
    Z_tetrimino=
    {
        {
            { 1,1, 0},
            { 0,1, 1} 
        },
        {
            { 0,1},
            { 1,1},
            { 1,0} 
        }
    },
    N_tetrimino=
    {
        {
            { 0,1,1},
            { 1,1,0}
        },
        {
            { 1,0},
            { 1,1},
            { 0,1} 
        }
    }
}



<Button.lua>

Button = Object:extend()
function Button:new(text,x,y)
    self.x=x
    self.y=y
    self.width =180
    self.height = 50
    self.text =text
    self.hovercolor = "3161A3"
    self.textcolor = "#ffffff"
    self.font = love.graphics.newFont("UNSII-2.ttf",21)
end
function Button:isMouseHover()
    local x,y = love.mouse.getPosition()
    return x>self.x and x<self.x+self.width and y>self.y and y <self.y+self.height
end
--600,50,180,50
function Button:draw()
    if self:isMouseHover() then
        love.graphics.setColor(blockSetcolor(self.hovercolor))
    end
    
    love.graphics.rectangle("line",self.x,self.y,self.width,self.height)
    --love.graphics.setColor(blockSetcolor(self.textcolor))
    --local font = love.graphics.setFont(self.font,21)
    love.graphics.setFont(self.font)
    local textwidth = self.font:getWidth(self.text)
    local textheight = self.font:getHeight(self.text)
    love.graphics.print(self.text,self.x+(self.width-textwidth)/2,self.y+(self.height-textheight)/2)
    
    love.graphics.setColor(blockSetcolor(backColor))
end

<Block.lua>

Block = Object:extend()
Blockstate = true
function block_new()
    
    shape_num =1 
    Blockstate = true
    Tetrimino.x=7
    Tetrimino.y = 0
    local num = math.random(1, 6)
    local name = TetriminoName[num]
    X_Tetrimino=Tetrimino[name]
    return X_Tetrimino[1],X_Tetrimino
end
function blockSetcolor(hex)
    hex = hex:gsub("#","")
    if string.len(hex)==6 then
        local r = tonumber(hex:sub(1, 2), 16) / 255
        local g = tonumber(hex:sub(3, 4), 16) / 255
        local b = tonumber(hex:sub(5, 6), 16) / 255
        return r,g,b
    else
        error("Invalid hex color format. Use #RRGGBB format.")
    end
end
-- 方块旋转
function block_rotate(x,y)
    local preshape = nil
    shape_num = shape_num % #new_Tetrimino+1
    preshape = new_Tetrimino[shape_num]
    if not isCollide(x, y, preshape) then
        --清除信息
        clearOld()
        shape = preshape
    else
        shape_num = shape_num - 1
    end
    
end

--方块自动下落
function block_drop(x,y)
    if isCollide(x, y, shape) then
        --清除信息
        block_fixed(x, y)
    else
        clearOld()
        updateNEW(x, y)
    end
end

--碰撞检测
function isCollide(x, y, t)
    for i = 1, #t, 1 do
        for j = 1, #t[i], 1 do
            if t[i][j] == 1 and blockList[y + i][x + j] == 2 then
                return true
            end
        end
    end
    return false
end


--清除信息
function clearOld()
    for i = 1, #shape, 1 do
        for j = 1, #shape[i], 1 do
            if blockList[Tetrimino.y + i][Tetrimino.x + j] == 1 then
                blockList[Tetrimino.y + i][Tetrimino.x + j] = 0
            end
            
        end
    end
end

--方块死亡
function block_fixed(x,y)
    for i = 1, #shape, 1 do
            for j = 1, #shape[i], 1 do
                if shape[i][j]==1  then
                    blockList[y + i-1][x + j] = 2
                end
                
            end
    end
    Blockstate =false
end

--更新信息
function updateNEW(x,y)
    
    Tetrimino.x = x
    Tetrimino.y = y
        for i = 1, #shape, 1 do
            for j = 1, #shape[i], 1 do
                if shape[i][j]==1 then
                    blockList[y + i][x + j] = shape[i][j]
                end
            end
        end
end
function Issametable(x,y)
    for i=1,#x,1 do 
        if x[i]~=y[i] then
            return false
        end
    end
    return true
end

-- 消除函数
function block_remove()
    for i = 18, 19, 1 do
        if Issametable(blockList[i],Removerow) then
            table.remove(blockList,i)
            table.insert(blockList,1,newrow)
            Playersocre = Playersocre + 10
        end

    end
end

function Preblockdraw()
    for i = 1, #NextShape, 1 do
        for j = 1, #NextShape[i], 1 do
            if NextShape[i][j]==1 then
                love.graphics.rectangle("fill",  650+(j-1)* 31, 80+(i - 1) * 31, 30, 30)
            end
        end
    end
end

-- 错误想法:想单独弄tetrimino的降落动画,其实只要传递数据,最后根据二维数组,让绘制函数绘制就行
function draw_Blcok()
    for i = 1 , 19 ,1 do
        for j =1 ,19 ,1 do
            if blockList[i][j]==1 then
                love.graphics.rectangle("fill", (j - 2) * 31, (i - 1) * 31, 30, 30)
            elseif blockList[i][j]==2 then
                love.graphics.setColor(blockSetcolor("F94A29"))
                love.graphics.rectangle("fill", (j - 2) * 31, (i - 1) * 31, 30, 30)
                love.graphics.setColor(blockSetcolor(backColor))
            end
        end
    end
    
end

<Classic.lua>

自行寻找🙂

;