logo CoderDojo

Löve2d - Blocks

We gaan met 2D game framework Löve2D een spel met vallende blokken maken.

voorbeeld van het spel

De regels

voorbeeld van de stukken

Er zijn 7 verschillende stukken. Elk stuk bestaat uit 4 kleinere blokken.

Stukken vallen vanaf boven het speelveld. De speler kan het stuk verplaatsen naar links en rechts en het stuk ronddraaien. Als een stuk is geland, valt er een volgend stuk.

Boven het speelveld wordt de vorm van het volgende stuk dat gaat vallen getoond.

voorbeeld van de vorm van het volgende stuk

Als er een volledig gesloten rij van blokken is gevormd van links naar rechts op het speelveld, verdwijnt het en schuiven alle blokken erboven 1 rij naar beneden.

Het spel eindigt als een nieuw stuk direct op een al liggend stuk terecht zou komen.

Controls

toetsactie
pijl linksbeweeg links
pijl rechtsbeweeg rechts
zdraai tegen de klok in
xdraai met de klok mee
cval op liggende blokken

Overzicht

Een rooster bevat de blokken die al zijn gevallen. Een vak in het rooster kan leeg zijn of gevuld met een blok van een bepaalde kleur.

Tekst ' ' (spatie) is een leeg vak zonder blok en 'i', 'j', 'l', 'o', 's', 't' en 'z' zijn vakken met een blok met verschillende kleuren.

voorbeeld het rooster met daarin de blokken

Alle verschillende stukken worden in het rooster opgeslagen met de hoe ze gedraaid waren toen ze vielen.

voorbeeld van gedraaide stukken

Een vallend stuk wordt opgeslagen met een getal dat aangeeft welk type stuk het is, hoe het gedraaid is en wat de positie is in het rooster. Voor de positie worden X en Y waarden gebruikt.

Een nieuw stuk wordt boven het rooster getoond, maar niet als er te weinig voor is door al gestapelde blokken. In dat geval is het spel afgelopen.

De speler kan de stukken verplaatsen naar links en rechts, maar niet als het een stuk dat al gestapeld is overlapt. Een stuk kan ook niet buiten het rooster verplaatst worden.

Als er wat tijd is verlopen, verplaatst het stuk één rij blokken naar beneden. Dit gebeurt alleen als de nieuwe positie van het stuk niet overlapt met al gestapelde blokken en niet buiten het rooster valt.

Als knop z of x wordt ingedrukt, draait het stuk tegen de klok in of met de klok mee. Maar niet als het stuk dan overlapt met al gestapelde blokken of buiten het rooster valt.

Als de val knop c wordt ingedrukt, gaat het stuk sneller naar beneden, totdat het landt op al geplaatste stukken.

Als het stuk is geland, wordt een nieuw stuk gemaakt boven het rooster.

De 7 verschillende stukken worden in een willekeurige volgorde aangeboden.

Programmeren

Voor het programmeren zelf heb je alleen een editor zoals bijvoorbeeld Visual Studio Code nodig. Maar om het programma uit te voeren, heb je het Löve2D programma nodig. Deze kun je downloaden vanaf de site https://love2d.org/. Kijk voor meer informatie over het installeren op /instructies/love2d-shooter/#installatie-löve.

Als je Löve2D hebt geïnstalleerd, open je een terminal of cmd op Windows en zou je love.exe moeten kunnen starten.

Bij het starten van het script is het belangrijk om te onthouden dat je de directory meegeeft aan Love en niet main.lua.
Dus:

love.exe blocks/

Hierbij bevat blocks het bestand main.lua waarin we de code schrijven van de volgende hoofdstukken.

Het venster tekenen

We beginnen in een lege conf.lua met:

function love.conf(t)
    t.window.width = 20 * 14
    t.window.height = 20 * 25
end

conf.lua

en een lege main.lua.

Als je nu love.exe blocks/ uitvoert, krijg je een leeg rechthoekig scherm.

Het rooster tekenen

Voor ieder blok in het speelveld wordt een vierkant getekend.

Zet de volgende code in main.lua:

function love.draw()
    for y = 1, 18 do
        for x = 1, 10 do
            local blockSize = 20
            local blockDrawSize = blockSize - 1
            love.graphics.rectangle(
                'fill',
                (x - 1) * blockSize,
                (y - 1) * blockSize,
                blockDrawSize,
                blockDrawSize
            )
        end
    end
end

main.lua het rooster

Het rooster inkleuren

De achtergrondkleur en de kleur van een leeg vak worden ingesteld.

Voeg functie love.load() toe aan main.lua, boven functie love.draw() die er al in staat:

function love.load()
    love.graphics.setBackgroundColor(255, 255, 255)
end

main.lua

Pas nu functie love.draw() aan:


function love.draw()
    for y = 1, 18 do
        for x = 1, 10 do
            -- voeg toe:
            love.graphics.setColor(.87, .87, .87)
            --
            
            local blockSize = 20
            local blockDrawSize = blockSize - 1
            love.graphics.rectangle(
                'fill',
                (x - 1) * blockSize,
                (y - 1) * blockSize,
                blockDrawSize,
                blockDrawSize
            )
        end
    end
end

main.lua het ingekleurde rooster

De gevallen blokken bewaren

Het rooster voor de gevallen blokken is gemaakt en elk blok wordt als ' ' (spatie) gezet wat betekend dat het vak leeg is.

We gaan de breedte en hoogte van het rooster gebruiken om de blokken te tekenen, dus we zetten de waarden 10 en 18 in variabelen.

Pas de functies aan met:

function love.load()
    love.graphics.setBackgroundColor(255, 255, 255)

    -- voeg onderstaande toe:
    gridXCount = 10
    gridYCount = 18

    inert = {}
    for y = 1, gridYCount do
        inert[y] = {}
        for x = 1, gridXCount do
            inert[y][x] = ' '
        end
    end
    --
end

function love.draw()
    -- vervang:
    for y = 1, 18 do
        for x = 1, 10 do

    -- door:
    for y = 1, gridYCount do
        for x = 1, gridXCount do
    --
end

main.lua

De kleur van de blokken

De kleur van de blokken die we gaan tekenen wordt bepaald door het type.

Om dit testen, zetten we verschillende typen blokken in het rooster.
De types geven we aan met een letter: i, j, l, o.. etc. Vervolgens koppelen we een kleur aan elk type.
Bijvoorbeeld:

i = {.47, .76, .94},

Maak de volgende aanpassingen:

function love.load()
    -- voeg toe:
    inert[18][1] = 'i'
    inert[17][2] = 'j'
    inert[16][3] = 'l'
    inert[15][4] = 'o'
    inert[14][5] = 's'
    inert[13][6] = 't'
    inert[12][7] = 'z'
    --
end

function love.draw()
    for y = 1, gridYCount do
        for x = 1, gridXCount do
            -- vervang:
            love.graphics.setColor(.87, .87, .87)
            -- door dit:
            local colors = {
                [' '] = {.87, .87, .87},
                i = {.47, .76, .94},
                j = {.93, .91, .42},
                l = {.49, .85, .76},
                o = {.92, .69, .47},
                s = {.83, .54, .93},
                t = {.97, .58, .77},
                z = {.66, .83, .46},
            }
            local block = inert[y][x]
            local color = colors[block]
            love.graphics.setColor(color)
            -- tot hier toe.

            local blockSize = 20
            local blockDrawSize = blockSize - 1
            love.graphics.rectangle(
                'fill',
                (x - 1) * blockSize,
                (y - 1) * blockSize,
                blockDrawSize,
                blockDrawSize
            )
            --
        end
    end
end

main.lua het rooster met blokken in de verschillende kleuren

De stukken opslaan

Een puzzelstuk bestaat uit verschillende blokken. We slaan de variaties van puzzelstuk op als een nieuw raster. In dit raster kunnen we ook de gedraaide versies van het puzzelstuk bewaren.

{
    {' ', ' ', ' ', ' '},
    {'i', 'i', 'i', 'i'},
    {' ', ' ', ' ', ' '},
    {' ', ' ', ' ', ' '},
}

Elk puzzelstuk kan 4 varianten hebben, want hij kan 4 kanten op gedraaid zijn. We slaan dus 4 varianten op voor elk puzzelstuk. In het geval van de rechte lijn zijn dit er eigenlijk maar 2. Deze vorm is namelijk symmetrisch over 1 as. De kubus heeft maar 1 variant. Deze is namelijk symmetrisch over 2 assen. De rechte lijn ziet er als volgt uit:

{
    {
        {' ', ' ', ' ', ' '},
        {'i', 'i', 'i', 'i'},
        {' ', ' ', ' ', ' '},
        {' ', ' ', ' ', ' '},
    },
    {
        {' ', 'i', ' ', ' '},
        {' ', 'i', ' ', ' '},
        {' ', 'i', ' ', ' '},
        {' ', 'i', ' ', ' '},
    },
}

Dit zijn alle puzzelstukken; voeg deze toe aan functie love.load():

function love.load()
    love.graphics.setBackgroundColor(255, 255, 255)
    pieceStructures = {
        {
            {
                {' ', ' ', ' ', ' '},
                {'i', 'i', 'i', 'i'},
                {' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'i', ' ', ' '},
                {' ', 'i', ' ', ' '},
                {' ', 'i', ' ', ' '},
                {' ', 'i', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {' ', 'o', 'o', ' '},
                {' ', 'o', 'o', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {'j', 'j', 'j', ' '},
                {' ', ' ', 'j', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'j', ' ', ' '},
                {' ', 'j', ' ', ' '},
                {'j', 'j', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {'j', ' ', ' ', ' '},
                {'j', 'j', 'j', ' '},
                {' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'j', 'j', ' '},
                {' ', 'j', ' ', ' '},
                {' ', 'j', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {'l', 'l', 'l', ' '},
                {'l', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'l', ' ', ' '},
                {' ', 'l', ' ', ' '},
                {' ', 'l', 'l', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', ' ', 'l', ' '},
                {'l', 'l', 'l', ' '},
                {' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {'l', 'l', ' ', ' '},
                {' ', 'l', ' ', ' '},
                {' ', 'l', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {'t', 't', 't', ' '},
                {' ', 't', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 't', ' ', ' '},
                {' ', 't', 't', ' '},
                {' ', 't', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 't', ' ', ' '},
                {'t', 't', 't', ' '},
                {' ', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 't', ' ', ' '},
                {'t', 't', ' ', ' '},
                {' ', 't', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {' ', 's', 's', ' '},
                {'s', 's', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {'s', ' ', ' ', ' '},
                {'s', 's', ' ', ' '},
                {' ', 's', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
        {
            {
                {' ', ' ', ' ', ' '},
                {'z', 'z', ' ', ' '},
                {' ', 'z', 'z', ' '},
                {' ', ' ', ' ', ' '},
            },
            {
                {' ', 'z', ' ', ' '},
                {'z', 'z', ' ', ' '},
                {'z', ' ', ' ', ' '},
                {' ', ' ', ' ', ' '},
            },
        },
    }

main.lua

Het vallende stuk opslaan

Er kan altijd maar een puzzelstuk tegelijkertijd vallen. We kunnen dit puzzelstuk opslaan met de variabelen pieceType en pieceRotation. pieceType bevat welk stuk het is (lijn, blokje, etc). pieceRotation bevat hoe het stuk gedraaid is.

function love.load()
    -- voeg toe aan het einde van de functie:
    pieceType = 1
    pieceRotation = 1
    --
end

main.lua

Het stuk tekenen

Elk puzzelstuk wordt getekend door langs elk vakje in het rooster te gaan en de blokken een voor een in te kleuren.
De kleur is afhankelijk van het type blok.

function love.draw()

    -- voeg dit hele stuk code toe onderaan deze functie
    for y = 1, 4 do
        for x = 1, 4 do
            local block = pieceStructures[pieceType][pieceRotation][y][x]
            if block ~= ' ' then
                local colors = {
                    i = {.47, .76, .94},
                    j = {.93, .91, .42},
                    l = {.49, .85, .76},
                    o = {.92, .69, .47},
                    s = {.83, .54, .93},
                    t = {.97, .58, .77},
                    z = {.66, .83, .46},
                }
                local color = colors[block]
                love.graphics.setColor(color)

                local blockSize = 20
                local blockDrawSize = blockSize - 1
                love.graphics.rectangle(
                    'fill',
                    (x - 1) * blockSize,
                    (y - 1) * blockSize,
                    blockDrawSize,
                    blockDrawSize
                )
            end
        end
    end
    --
end

main.lua

En verwijder dit stuk uit functie love.load():

    inert[18][1] = 'i'
    inert[17][2] = 'j'
    inert[16][3] = 'l'
    inert[15][4] = 'o'
    inert[14][5] = 's'
    inert[13][6] = 't'
    inert[12][7] = 'z'

main.lua

het rooster

Code vereenvoudigen

De code voor het tekenen van een stilstaand blok lijkt erg op de code voor het tekenen van een vallend blok. We voegen daarom de functie drawBlock toe en verplaatsen de code in de twee for-loops er naartoe.

Vervang alle code in love.draw() met deze code:

function love.draw()
    local function drawBlock(block, x, y)
        local colors = {
            [' '] = {.87, .87, .87},
            i = {.47, .76, .94},
            j = {.93, .91, .42},
            l = {.49, .85, .76},
            o = {.92, .69, .47},
            s = {.83, .54, .93},
            t = {.97, .58, .77},
            z = {.66, .83, .46},
        }
        local color = colors[block]
        love.graphics.setColor(color)

        local blockSize = 20
        local blockDrawSize = blockSize - 1
        love.graphics.rectangle(
            'fill',
            (x - 1) * blockSize,
            (y - 1) * blockSize,
            blockDrawSize,
            blockDrawSize
        )
    end

    for y = 1, gridYCount do
        for x = 1, gridXCount do
            drawBlock(inert[y][x], x, y)
        end
    end

    for y = 1, 4 do
        for x = 1, 4 do
            local block = pieceStructures[pieceType][pieceRotation][y][x]
            if block ~= ' ' then
                drawBlock(block, x, y)
            end
        end
    end
end

main.lua

Het stuk draaien

Wanneer we op de x-toets drukken willen wat dat het stuk draait.

pieceRotation bevat een getal dat de draaiing aangeeft. 1 is geen draaiing, 2 is 90° met de klok mee en 3 is 180°.

Als het rotation getal hoger is dan de verschillende draai posities (4, 2 of 1). Dan wordt het getal terug gezet naar 1.

Als we op z-toets drukken willen we dat het het stuk de andere kant op draait. Dit doen we door de waarde in pieceRotation met 1 te verlagen.

Als het getal lager wordt dan 0. Zetten we het weer terug naar 1.

Voeg dit stuk code toe onderaan het bestand:

function love.keypressed(key)
    if key == 'x' then
        pieceRotation = pieceRotation + 1
        if pieceRotation > #pieceStructures[pieceType] then
            pieceRotation = 1
        end

    elseif key == 'z' then
        pieceRotation = pieceRotation - 1
        if pieceRotation < 1 then
            pieceRotation = #pieceStructures[pieceType]
        end
    end
end

main.lua

het rooster

De stukken testen

We willen graag de code testen. Daarom voegen we toe dat we het puzzelstuk kunnen veranderen met de omhoog en omlaag pijltjestoetsen.
De tijdelijke code onderaan functie love.keypressed(key):

function love.keypressed(key)
    -- vervang laatste twee "end" regels met onderstaande tijdelijke code:
    elseif key == 'down' then
        pieceType = pieceType + 1
        if pieceType > #pieceStructures then
            pieceType = 1
        end
        pieceRotation = 1
    elseif key == 'up' then
        pieceType = pieceType - 1
        if pieceType < 1 then
            pieceType = #pieceStructures
        end
        pieceRotation = 1
    end
    --
end

main.lua

het rooster Als je hebt gezien dat de stukken veranderen, kun je de net toegevoegde code weer verwijderen.

De positie van het vallende stuk bijhouden

We bewaren de positie van het vallende puzzelstuk op in het rooster en we tekenen het puzzelstuk op die positie.

function love.load()
    -- voeg deze regels toe onderaan deze functie
    pieceX = 3
    pieceY = 0
    --
end

En het tekenen van het blok op de juiste positie:

function love.draw()
    -- vervang:
                drawBlock(block, x, y)
                
    -- met:
                drawBlock(block, x + pieceX, y + pieceY)
    --
end

main.lua

het rooster

Het stuk verplaatsen

We gaan nu de linker en rechter pijltjestoetsen gebruiken om het stuk te verplaatsen.

Voeg de volgende regels toe onderaan love.keypressed(key):

function love.keypressed(key)
    -- vervang de een na laatste "end" met: 
    elseif key == 'left' then
        pieceX = pieceX - 1

    elseif key == 'right' then
        pieceX = pieceX + 1
    end
    --
end

main.lua

het rooster

Timer

We willen dat elke 0.5 seconden het puzzelstuk valt.

We maken daarom een timer variabele, die we steeds met dt (delta time: de tijd die verstreken is) verhogen.

Als de waarde van timer gelijk aan 0.5 of hoger is, zetten we het terug naar 0.

function love.load()
    -- onderaan deze functie:
    timer = 0
    --
end

En een nieuwe functie onderaan het bestand:

function love.update(dt)
    timer = timer + dt
    if timer >= 0.5 then
        timer = 0
        -- tijdelijk; kijk naar de console/cmd window voor een tick elke halve seconde
        print('tick')
        --
    end
end

main.lua
Na het testen mag je print('tick') weer weghalen.

Stukken vallen

We gebruiken de timer om het stuk elke 0.5 seconden verder naar beneden te laten vallen.
De waarde van pieceY wordt met 1 verhoogd om het stuk 1 regel lager te tekenen:

function love.update(dt)
    timer = timer + dt
    if timer >= 0.5 then
        timer = 0
        -- voeg toe:
        pieceY = pieceY + 1
        --
    end
end

main.lua

het rooster

Linker, rechter en onderrand van het rooster

We willen voorkomen dat de puzzelstukken links of rechts van het scherm af kunnen bewegen. Daarom checken we altijd eerst of alle blokken nog wel in het speelveld zijn.

We willen deze check vaker uitvoeren, daarom stoppen we het in een functie. Dan kunnen we het makkelijk herbruiken. De functie heeft de positie en rotatie nodig. De functie geeft true of false terug om aan te geven of we mogen bewegen of draaien.

We noemen de functie: canpieceMove(testX, testY, testRotation). Voor nu zal de functie alleen true teruggeven, dan kunnen we namelijk de functie in de rest van de code verwerken.

We passen de code aan zodat de check functie eerst wordt uitgevoerd, voordat we bewegen of draaien.

function love.load()
    -- voeg aan het einde van de functie toe:
    function canPieceMove(testX, testY, testRotation)
        return true
    end
    --
end

function love.update(dt)
    timer = timer + dt
    if timer >= 0.5 then
        timer = 0

        -- vervang:
        pieceY = pieceY + 1
        
        -- door:
        local testY = pieceY + 1
        if canPieceMove(pieceX, testY, pieceRotation) then
            pieceY = testY
        end
        --
    end
end

function love.keypressed(key)
    elseif key == 'left' then
        -- vervang:
        pieceX = pieceX - 1
        
        -- door:
        local testX = pieceX - 1

        if canPieceMove(testX, pieceY, pieceRotation) then
            pieceX = testX
        end
        --
    elseif key == 'right' then
        -- vervang:
        pieceX = pieceX + 1

        -- door:        
        local testX = pieceX + 1

        if canPieceMove(testX, pieceY, pieceRotation) then
            pieceX = testX
        end
        --
    end
end

main.lua

We beginnen met links checken. Als het blok niet leeg is, en de x positie lager is dan 1, geeft de functie false terug.

function love.load()
    -- vervang functie canPieceMove in zijn geheel door:
    function canPieceMove(testX, testY, testRotation)
        for y = 1, 4 do
            for x = 1, 4 do
                if pieceStructures[pieceType][testRotation][y][x] ~= ' '
                        and (testX + x) < 1 then
                    return false
                end
            end
        end

        return true
    end
    --
end

main.lua

Code vereenvoudigen

Het aantal blokken van elk puzzelstuk op de X en Y worden herbruikt uit het tekenen. We stoppen deze waardes in variabelen.

function love.load()
    -- vervang functie canPieceMove weer, maar nu in zijn geheel door:
    pieceXCount = 4
    pieceYCount = 4

    function canPieceMove(testX, testY, testRotation)
        for y = 1, pieceYCount do
            for x = 1, pieceXCount do
                if pieceStructures[pieceType][testRotation][y][x] ~= ' '
                        and (testX + x) < 1 then
                    return false
                end
            end
        end

        return true
    end
    --
end

function love.draw()
    -- vervang:
    for y = 1, 4 do
        for x = 1, 4 do
    -- door:    
    for y = 1, pieceYCount do
        for x = 1, pieceXCount do
    --
            local block = pieceStructures[pieceType][pieceRotation][y][x]
            if block ~= ' ' then
                drawBlock(block, x + pieceX, y + pieceY)
            end
        end
    end
end

main.lua

Rechts

We gaan nu rechts checken. Als het blok niet leeg is, en de x positie groter is dan het grid, geeft de functie false terug.

function love.load()

    function canPieceMove(testX, testY, testRotation)
        for y = 1, pieceYCount do
            for x = 1, pieceXCount do
                -- vervang
                if pieceStructures[pieceType][testRotation][y][x] ~= ' '
                        and (testX + x) < 1 then
                    return false
                end
                -- door:
                if pieceStructures[pieceType][testRotation][y][x] ~= ' ' and 
                    ((testX + x) < 1 or (testX + x) > gridXCount)
                then
                    return false
                end
                --
            end
        end

        return true
    end
end

main.lua

Onderkant

Het puzzelstuk moet stoppen als het de onderkant aanraak. Als de onderkant van het puzzelstuk lager is dan de onderkant van het veld geeft de functie false terug.

function love.load()

    function canPieceMove(testX, testY, testRotation)
        for y = 1, pieceYCount do
            for x = 1, pieceXCount do
                -- vervang:
                if pieceStructures[pieceType][testRotation][y][x] ~= ' ' and 
                    ((testX + x) < 1 or (testX + x) > gridXCount)
                then
                    return false
                end
                -- door:
                if pieceStructures[pieceType][testRotation][y][x] ~= ' ' and 
                    (
                        (testX + x) < 1
                        or (testX + x) > gridXCount
                        or (testY + y) > gridYCount
                    ) 
                then
                    return false
                end
                --
            end
        end

        return true
    end
end

main.lua

Controle op botsingen

Als er al een ander puzzelstuk is, kan het puzzelstuk daar niet naartoe bewegen. Daarom checken we of er al een blok is op die plek. Als dat zo is, geeft de functie ook false terug.

We testen dit door zelf een blokje op het rooster te zetten.

function love.load()

    function canPieceMove(testX, testY, testRotation)
        for y = 1, pieceYCount do
            for x = 1, pieceXCount do
                if pieceStructures[pieceType][testRotation][y][x] ~= ' ' and
                    -- vervang: 
                    (
                        (testX + x) < 1
                        or (testX + x) > gridXCount
                        or (testY + y) > gridYCount
                    ) 
                    -- door:
                    (
                        (testX + x) < 1
                        or (testX + x) > gridXCount
                        or (testY + y) > gridYCount
                        or inert[testY + y][testX + x] ~= ' '
                    )
                    -- 
                then
                    return false
                end
            end
        end

        return true
    end

    -- tijdelijk, om toegevoegde voorwaarde te testen
    inert[8][5] = 'z'
    --
end

main.lua

Als het stuk valt, wordt het geblokkeerd door het blok. Schuif het stuk eens naar links of rechts. Wat gebeurt er?

Verwijder na het testen de tijdelijke code.

het rooster

Code vereenvoudigen

De posities van de blokken die we testen kunnen we herbruiken. We stoppen deze in variabelen.

function love.load()

    function canPieceMove(testX, testY, testRotation)
        for y = 1, pieceYCount do
            for x = 1, pieceXCount do
                -- vervang:
                if pieceStructures[pieceType][testRotation][y][x] ~= ' ' and
                    (
                        (testX + x) < 1
                        or (testX + x) > gridXCount
                        or (testY + y) > gridYCount
                        or inert[testY + y][testX + x] ~= ' '
                    )
                then
                    return false
                end
                -- door:                
                local testBlockX = testX + x
                local testBlockY = testY + y

                if pieceStructures[pieceType][testRotation][y][x] ~= ' ' and 
                    (
                        testBlockX < 1
                        or testBlockX > gridXCount
                        or testBlockY > gridYCount
                        or inert[testBlockY][testBlockX] ~= ' '
                    )
                then
                    return false
                end
                --
            end
        end

        return true
    end
end

main.lua

Sneller vallen

Als de speler c indrukt laten we het puzzelstuk snel vallen. Zolang c is ingedrukt verhogen we de Y positie met 1 totdat het puzzelstuk iets raakt.

function love.keypressed(key)
    -- voeg
    elseif key == 'c' then
        while canPieceMove(pieceX, pieceY + 1, pieceRotation) do
            pieceY = pieceY + 1
        end
    -- toe voor de één na laatste "end"
    end
end

main.lua

Een stuk terugzetten

Als de timer tikt, en het puzzelstuk niet verder kan bewegen, resetten we de positie en rotatie en type.

function love.update(dt)
    timer = timer + dt
    if timer >= 0.5 then
        timer = 0
        local testY = pieceY + 1
        -- vervang:
        if canPieceMove(pieceX, testY, pieceRotation) then
            pieceY = testY
        end
        -- door:
        if canPieceMove(pieceX, testY, pieceRotation) then
            pieceY = testY
        else
            pieceX = 3
            pieceY = 0
            pieceType = 1
            pieceRotation = 1
        end
        --
    end
end

main.lua

Code vereenvoudigen

We resetten het puzzelstuk vaker, dus we stoppen de code in een functie om her te gebruiken.

function love.load()
    -- voeg toe aan einde van functie 
    function newPiece()
        pieceX = 3
        pieceY = 0
        pieceType = 1
        pieceRotation = 1
    end

    newPiece()
    -- 
end

function love.update(dt)
    timer = timer + dt
    if timer >= 0.5 then
        timer = 0

        local testY = pieceY + 1
        if canPieceMove(pieceX, testY, pieceRotation) then
            pieceY = testY
        else
        -- vervang:
            pieceX = 3
            pieceY = 0
            pieceType = 1
            pieceRotation = 1
        -- door:
            newPiece()
        --
        end
    end
end

main.lua

Een reeks met volgende stukken

We maken nu een sequence (volgorde) waarin de puzzelstukken gaan vallen. We slaan deze op als een tabel met getallen die aangeven welk puzzelstuk het is.

We stoppen alle verschillende nummers/puzzelstuk types in de tabel op een willekeurige positie.

We testen dit door een reeks te maken en te printen als je op s drukt

function love.load()
    -- voeg toe aan einde van functie 

    function newSequence()
        sequence = {}
        for pieceTypeIndex = 1, #pieceStructures do
            local position = love.math.random(#sequence + 1)
            table.insert(
                sequence,
                position,
                pieceTypeIndex
            )
        end
    end
    newSequence()
    --
end

function love.keypressed(key)
    -- tijdelijke code
    elseif key == 's' then
        newSequence()
        print(table.concat(sequence, ', '))
    --
    end
end

main.lua

Resultaat van drukken op knop s in tijdelijke code:

3, 2, 4, 1, 7, 5, 6

Verwijder de tijdelijke code na het testen.

Een nieuw stuk uit de reeks

Wanneer er een nieuw puzzelstuk wordt gemaakt, halen we de vorige uit de reeks en herbruiken we het.

Als de reeks leeg is, is maken we een nieuwe.

Let op! We verplaatsen de newPiece functie naar onder de newSequence functie.

function love.load()

    -- vervang:
    function newPiece()
        pieceX = 3
        pieceY = 0
        pieceType = 1
        pieceRotation = 1
    end
    -- door:
    function newPiece()
        pieceX = 3
        pieceY = 0
        pieceRotation = 1
        pieceType = table.remove(sequence)

        if #sequence == 0 then
            newSequence()
        end
    end
    --
    newPiece()
end

main.lua

Gevallen stukken bewaren

Als een stuk is gevallen, worden ze apart gezet en bewaard.

Elk blok in het stuk wordt bekeken en als het niet leeg is, wordt het blok als bezet gezet.

function love.update(dt)
    timer = timer + dt
    if timer >= 0.5 then
        timer = 0

        local testY = pieceY + 1
        if canPieceMove(pieceX, testY, pieceRotation) then
            pieceY = testY
        else
            -- voeg toe
            for y = 1, pieceYCount do
                for x = 1, pieceXCount do
                    local block =
                        pieceStructures[pieceType][pieceRotation][y][x]
                    if block ~= ' ' then
                        inert[pieceY + y][pieceX + x] = block
                    end
                end
            end
            --
            newPiece()
        end
    end
end

main.lua

Een nieuw stuk direct na landing vorige

Als een stuk is gevallen, wordt de timer direct opnieuw ingesteld, zodat een volgende stuk direct wordt gemaakt.

function love.load()
    -- vervang:
    pieceY = 0
    timer = 0

    pieceXCount = 4
    -- door:    
    pieceY = 0
    timer = 0
    timerLimit = 0.5

    pieceXCount = 4
    -- 
end

function love.update(dt)
    -- vervang:
    timer = timer + dt
    if timer >= 0.5 then
    -- door:
    timer = timer + dt
    if timer >= timerLimit then
    -- 
end

function love.keypressed(key)
    elseif key == 'c' then
        -- vervang:
        while canPieceMove(pieceX, pieceY + 1, pieceRotation) do
            pieceY = pieceY + 1
        end
        -- door:        
        while canPieceMove(pieceX, pieceY + 1, pieceRotation) do
            pieceY = pieceY + 1
            timer = timerLimit
        end
        --
    end
end

main.lua

Volle rijen vinden

Elke rij wordt gecontroleerd. Als er geen legen blokken in de rij zijn, is de rij compleet.

Om te controleren of het werkt worden complete rijen geprint naar de console.

function love.update(dt)
    timer = timer + dt
    if timer >= timerLimit then
        timer = 0

        local testY = pieceY + 1
        if canPieceMove(pieceX, testY, pieceRotation) then
            pieceY = testY
        else
            for y = 1, pieceYCount do
                for x = 1, pieceXCount do
                    local block =
                        pieceStructures[pieceType][pieceRotation][y][x]
                    if block ~= ' ' then
                        inert[pieceY + y][pieceX + x] = block
                    end
                end
            end
            -- voeg toe:            
            for y = 1, gridYCount do
                local complete = true
                for x = 1, gridXCount do
                    if inert[y][x] == ' ' then
                        complete = false
                        break
                    end
                end
                
                -- tijdelijke code
                if complete then
                    print('Complete row: '..y)
                end
                --
            end
            --            
            newPiece()
        end
    end
end

main.lua

Volle rijen verwijderen

Als een rij compleet is,

If the row is complete, the rows from the complete row to the row second from the top are looped through.

Each block in the row is looped through and set to the value of the block above it. Because there is nothing above the top row it doesn’t need to be looped through.

The top row is then set to all empty blocks.

De volledige code tot op dit punt:

function love.update(dt)

            for y = 1, gridYCount do
                local complete = true
                for x = 1, gridXCount do
                    if inert[y][x] == ' ' then
                        complete = false
                        break
                    end
                end

                -- vervang:
                if complete then
                   -- tijdelijke code
                   print('Complete row: '..y)
                end
                -- door:
                if complete then
                    for removeY = y, 2, -1 do
                        for removeX = 1, gridXCount do
                            inert[removeY][removeX] =
                            inert[removeY - 1][removeX]
                        end
                    end

                    for removeX = 1, gridXCount do
                        inert[1][removeX] = ' '
                    end
                end
                --
            end
end

main.lua

Game over

Als een nieuw stuk meteen niet meer verplaatst kan worden omdat er andere blokken in de weg staan, is het spel over.

love.load wordt aangeroepen om het spel opnieuw te starten.

function love.update(dt)
            -- voeg toe aan het einde van deze functie
            if not canPieceMove(pieceX, pieceY, pieceRotation) then
                love.load()
            end
            --
        end
    end
end

main.lua

Het rooster verplaatsen in het window

Tot nu toe is het rooster aan de linker boven kant van het venster getekend. We gaan het nu in het midden een stukje van de bovenrand van het venster zetten.

function love.draw()
    -- vervang:
    for y = 1, gridYCount do
        for x = 1, gridXCount do
            drawBlock(inert[y][x], x, y)
        end
    end

    for y = 1, pieceYCount do
        for x = 1, pieceXCount do
            local block = pieceStructures[pieceType][pieceRotation][y][x]
            if block ~= ' ' then
                drawBlock(block, x + pieceX, y + pieceY)
            end
        end
    end
    -- door:    
    local offsetX = 2
    local offsetY = 5

    for y = 1, gridYCount do
        for x = 1, gridXCount do
            drawBlock(inert[y][x], x + offsetX, y + offsetY)
        end
    end

    for y = 1, pieceYCount do
        for x = 1, pieceXCount do
            local block = pieceStructures[pieceType][pieceRotation][y][x]
            if block ~= ' ' then
                drawBlock(block, x + pieceX + offsetX, y + pieceY + offsetY)
            end
        end
    end
    --
end

main.lua

het rooster

Het volgende stuk

Het volgende stuk uit de reeks dat gaat vallen, wordt boven het rooster getekend.

function love.draw()

    local function drawBlock(block, x, y)
        local colors = {
            [' '] = {.87, .87, .87},
            i = {.47, .76, .94},
            j = {.93, .91, .42},
            l = {.49, .85, .76},
            o = {.92, .69, .47},
            s = {.83, .54, .93},
            t = {.97, .58, .77},
            z = {.66, .83, .46},
            -- voeg toe:
            preview = {.75, .75, .75},
            --
        }
    -- voeg toe aan het eind van de functie
    for y = 1, pieceYCount do
        for x = 1, pieceXCount do
            local block = pieceStructures[sequence[#sequence]][1][y][x]
            if block ~= ' ' then
                drawBlock('preview', x + 5, y + 1)
            end
        end
    end
    --
end

main.lua

het rooster

Het spel herstarten

Als het spel is afgelopen, wordt het herstart. Daarvoor moeten wat variabelen opnieuw worden ingesteld.
De code hierovor zetten we in een functie.

function love.load()
    -- voeg toe aan het einde van de functie
    function reset()
        inert = {}
        for y = 1, gridYCount do
            inert[y] = {}
            for x = 1, gridXCount do
                inert[y][x] = ' '
            end
        end

        newSequence()
        newPiece()

        timer = 0
    end

    reset()
    --
end

function love.update(dt)
            -- vervang:
            if not canPieceMove(pieceX, pieceY, pieceRotation) then
                love.load()
            end
            -- met:
            if not canPieceMove(pieceX, pieceY, pieceRotation) then
                reset()
            end
            --
        end
    end
end

main.lua

Veel plezier met spelen!

Bron

Deze instructie is een vertaling van de Engelstalige tutorial Blocks: A tutorial for Lua and LÖVE 11 van simple.game.tutorials@gmail.com.

Licentie

Deze instructies worden, net als alle andere instructies van CoderDojo Nijmegen, aangeboden onder een Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Licentie.

Creative Commons License