Code Doodles!

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Trystan
Prole
Posts: 14
Joined: Fri Nov 24, 2023 9:30 am

Re: Code Doodles!

Post by Trystan »

Fairly new to löve but saw this thread and thought I'd try my hand at a quick doodle. Nothing too fancy, just some colourful lines around a square grid but I was quite happy with the result.

To add in an icon that draws in the middle and changes colour just uncomment the iconImg lines in load and draw.
Doodle.png
Doodle.png (161.32 KiB) Viewed 5485 times

Code: Select all

function love.load()
    windowHeight = love.graphics.getHeight()
    windowWidth = love.graphics.getWidth()
    frameSize = 400
    frameX = (windowWidth / 2) - (frameSize / 2)
    frameY = (windowHeight / 2) - (frameSize / 2)
    linesPerSide = 40 -- lines per side (i.e. total / 4)
    drawLines = math.floor(linesPerSide * 3.8) -- number of lines to draw each frame
    firstLine = 1
    delayTimer = 0
    delayTime = 0.03 -- seconds per update
    generateLines()
    -- iconImg = love.graphics.newImage("Cham.png")
end

function HSV(h, s, v)
    if s <= 0 then return v,v,v end
    h = h*6
    local c = v*s
    local x = (1-math.abs((h%2)-1))*c
    local m,r,g,b = (v-c), 0, 0, 0
    if h < 1 then r, g, b = c, x, 0
    elseif h < 2 then r, g, b = x, c, 0
    elseif h < 3 then r, g, b = 0, c, x
    elseif h < 4 then r, g, b = 0, x, c
    elseif h < 5 then r, g, b = x, 0, c
    else r, g, b = c, 0, x end
    return r+m, g+m, b+m
end

function generateLines()
    lines = {}
    local delta = frameSize / linesPerSide
    for i = 1, linesPerSide do
        lines[i] = {frameX, frameY + i * delta, frameX + frameSize - i * delta, frameY} -- top left
        lines[linesPerSide + i] = {frameX, frameY + i * delta, frameX + i * delta, frameY + frameSize} -- bottom left
        lines[linesPerSide * 2 + i] = {frameX + i * delta, frameY + frameSize, frameX + frameSize, frameY + frameSize - i * delta} -- bottom right
        lines[linesPerSide * 3 + i] = {frameX + frameSize, frameY + frameSize - i * delta, frameX + frameSize - i * delta, frameY} -- top right
    end
end

function love.update(dt)
    delayTimer = delayTimer + dt
    if delayTimer > delayTime then
        firstLine = firstLine + 1
        if firstLine > #lines then
            firstLine = 1
        end
        delayTimer = 0
    end
end

function love.draw()
    love.graphics.setLineWidth(1)
    local j, r, g, b
    -- draw the lines, rearmost first
    for i = 1, drawLines do
        j = (firstLine + i) % #lines + 1 -- set current line, starting at firstLine and wrapping
        r, g, b = HSV(j/#lines, 1, 1)
        love.graphics.setColor(r, g, b, i / drawLines) -- make early lines more transparent
        love.graphics.line(lines[j])
    end
    -- draw the icon, colour should still be the same as the foremost line
    -- love.graphics.draw(iconImg, windowWidth / 2, windowHeight / 2, 0, 1, 1, iconImg:getWidth() / 2, iconImg:getHeight() / 2)
    -- draw the frame
    love.graphics.setColor(1, 1, 1)
    love.graphics.setLineWidth(4)
    love.graphics.rectangle("line", frameX, frameY, frameSize, frameSize)
end
Last edited by Trystan on Fri Nov 24, 2023 9:45 am, edited 1 time in total.
Trystan
Prole
Posts: 14
Joined: Fri Nov 24, 2023 9:30 am

Re: Code Doodles!

Post by Trystan »

Another quick doodle. This is just a simple BSP generator but after 9 or 10 generations it creates quite a nice city grid like pattern. The partitions are stored in terms of percentage of the screen so it should resize gracefully.

Press 1 to reset the BSP tree
Press 2 to add new branches to the tree
Grid.jpg
Grid.jpg (133.41 KiB) Viewed 5462 times

Code: Select all

function love.load()
    love.window.setTitle("BSP")
    love.window.setMode(800, 600, {resizable = true})
    math.randomseed(os.time()) -- get the random warmed up
    math.random(); math.random(); math.random(); math.random()
    resetTree()
end

function love.resize(w, h)
    createTreeCoords()
end

function love.keypressed(key)
    if key == "1" then
        resetTree()
    elseif key == "2" then
        addGeneration()
    end
end

function resetTree()
    tree = {}
    tree[1] = {0, 0, 1, 1} -- add the root (the full area from 0,0 to 1,1)
    numGenerations = 1
    lastGenStart = 1
    createTreeCoords()
end

function addGeneration()
    local leaf, split, ratio
    for i = lastGenStart, #tree do -- go through the last generation and adds children to them
        leaf= tree[i]
        ratio = ((leaf[3] - leaf[1]) / (leaf[4] - leaf[2])) - 0.5 -- set horiz/vert split probability based on ratio of parent container
        if math.random() >= ratio then -- vertical split
            split = math.random(35, 65) / 100 -- random between 0.35 and 0.65
            split = leaf[2] + ((leaf[4] - leaf[2]) * split)
            table.insert(tree, {leaf[1], leaf[2], leaf[3], split})
            table.insert(tree, {leaf[1], split, leaf[3], leaf[4]})
        else -- horizontal split
            split = math.random(35, 65) / 100 -- random between 0.35 and 0.65
            split = leaf[1] + ((leaf[3] - leaf[1]) * split)
            table.insert(tree, {leaf[1], leaf[2], split, leaf[4]})
            table.insert(tree, {split, leaf[2], leaf[3], leaf[4]})
        end
        table.insert(leaf, {#tree - 1, #tree}) -- register children with leaf
    end
    numGenerations = numGenerations + 1
    lastGenStart = 2 ^ (numGenerations - 1)
    createTreeCoords()
end

function createTreeCoords()
    treeCoords = {}
    local leaf, x1, y1, x2, y2
    local w = love.graphics.getWidth()
    local h = love.graphics.getHeight()
    for i = lastGenStart, #tree do -- only put the last generation in the draw table
        leaf = tree[i]
        x1 = math.floor((leaf[1] * w) + 0.5)
        y1 = math.floor((leaf[2] * h) + 0.5)
        x2 = math.floor((leaf[3] * w) + 0.5)
        y2 = math.floor((leaf[4] * h) + 0.5)
        table.insert(treeCoords, {x1, y1, x2, y1}) -- top line
        table.insert(treeCoords, {x1, y1, x1, y2}) -- left line
    end
end

function love.draw()
    for i = 1, #treeCoords do -- draw the last generation's top and left borders
        love.graphics.line(treeCoords[i])
    end
end
User avatar
darkfrei
Party member
Posts: 1181
Joined: Sat Feb 08, 2020 11:09 pm

Re: Code Doodles!

Post by darkfrei »

From here: viewtopic.php?p=258462#p258462

Code: Select all

-- Z-sections with T-crossings

love.window.setTitle ('Z-sections with T-crossings; press SPACE for one or X-C-V-B to make alot; Q to new')

function insertZ (x1,y1, x2,y2, x3,y3, x4,y4)
	local xmin, ymin, xmax, ymax = math.min(x1, x2),math.min(y1, y2), math.max(x1, x2),math.max(y1, y2)
	table.insert (lines, {xmin, ymin, xmax, ymax})
	xmin, ymin, xmax, ymax = math.min(x2, x3),math.min(y2, y3), math.max(x2, x3),math.max(y2, y3)
	table.insert (lines, {xmin, ymin, xmax, ymax})
	xmin, ymin, xmax, ymax = math.min(x3, x4),math.min(y3, y4), math.max(x3, x4),math.max(y3, y4)
	table.insert (lines, {xmin, ymin, xmax, ymax})
end


function createFirstZ ()
	if h > w then
		-- two horizontal
		local x = math.random (1,w-1)
		local y1 = math.random (1,h-2)
		local y2 = math.random (2,h-1)
		print (y1, y2)
		if y1 == y2 then
			y2 = y1+1
		end
		if math.random () > 0.5 then
			insertZ (0,y1, x,y1, x, y2, w,y2)
		else
			insertZ (0,y2, x,y2, x, y1, w,y1)
		end
	else
		local x1 = math.random (1,w-2)
		local x2 = math.random (2,w-1)
		local y = math.random (1,h-2)
		if x1 == x2 then
			x2 = x1+1
		end
		if math.random () > 0.5 then
			insertZ (x1,0, x1,y, x2, y, x2,h)
		else
			insertZ (x1,0, x1,y, x2, y, x2,h)
		end
	end
end

function isPointFree (x, y)
	for i, line in ipairs (lines) do
		if x >= line[1] and x <= line[3] and y >= line[2] and y <= line[4] then
			return false
		end
	end
	return true
end

function isPointEnd (x, y)
	for i, line in ipairs (lines) do
		if (x == line[1] and y == line[2]) or (x == line[3] and y == line[4]) then
			return true
		end
	end
	return false
end

function getCrossingH (x, y, side)
	local x1 = x
	while true do
		x1 = x1+side
		if x1 == 0 then return 0, false end
		if x1 == w then return w, false end
		local pointFree = isPointFree (x1, y)
		local isEnd = isPointEnd (x1, y)
		if not pointFree then
			if isEnd then
				return nil
			else
				return x1, true -- position
			end
		end
	end
end

function getCrossingV (x, y, side)
	local y1 = y
	while true do
		y1 = y1+side
		if y1 == 0 then return 0, false end
		if y1 == h then return h, false end
		local pointFree = isPointFree (x, y1)
		local isEnd = isPointEnd (x, y1)
		if not pointFree then
			if isEnd then
				return nil
			else
				return y1, true -- position
			end
		end
	end
end

function getRandomFreeY (x, y)
	local list = {}
	local y1 = y
	while true do
		y1 = y1 + 1
		if y1 == h then break end
		if not isPointFree (x, y1) then break end
		table.insert (list, y1)
	end
	y1 = y
	while true do
		y1 = y1 - 1
		if y1 == 0 then break end
		if not isPointFree (x, y1) then break end
		table.insert (list, y1)
	end
	if #list > 0 then
		return list[math.random(#list)]
	end
end

function getRandomFreeX (x, y)
	local list = {}
	local x1 = x
	while true do
		x1 = x1 + 1
		if x1 == w then break end
		if not isPointFree (x1, y) then break end
		table.insert (list, x1)
	end
	x1 = x
	while true do
		x1 = x1 - 1
		if x1 == 0 then break end
		if not isPointFree (x1, y) then break end
		table.insert (list, x1)
	end
	if #list > 0 then
		return list[math.random(#list)]
	end
end



function tryCreateNewZ ()
	local x = math.random (1,w-1)
	local y = math.random (1,h-1)
	if isPointFree (x, y) then
		if math.random () < 0.5 then
			-- two horizontal
			local x1, onLineA = getCrossingH (x, y, -1)
			if x1 then
				local y1 = getRandomFreeY (x, y)
				if y1 then
					local x2, onLineB = getCrossingH (x, y1, 1)
					if x2 and (onLineA or onLineB) then
						insertZ (x1,y, x,y, x, y1, x2,y1)
					end
				end
			end
		else
			-- two vertical
			local y1, onLineA = getCrossingV (x, y, -1)
			if y1 then
				local x1 = getRandomFreeX (x, y)
				if x1 then
					local y2, onLineB = getCrossingV (x1, y, 1)
					if y2 and (onLineA or onLineB) then
						insertZ (x,y1, x,y, x1, y, x1,y2)
					end
				end
			end
		end
	end
end

function love.load ()
	w, h = 79, 59
	lines = {}
	createFirstZ ()
end


function love.draw ()
	local gridSize = 10
	love.graphics.translate (5,5)
	love.graphics.scale (gridSize)
	love.graphics.setLineWidth (2/gridSize)
	love.graphics.rectangle ('line', 0,0, w,h)
	for i, line in ipairs (lines) do
		love.graphics.line (line)
	end
end



function love.keypressed (key, scancode, isrepeat)
	if key == 'escape' then love.event.quit () end
	if key == 'space' then
		tryCreateNewZ ()
	elseif scancode == 'x' then
		for i = 1, 10 do
			tryCreateNewZ ()
		end
	elseif scancode == 'c' then
		for i = 1, 100 do
			tryCreateNewZ ()
		end
	elseif scancode == 'v' then
		for i = 1, 1000 do
			tryCreateNewZ ()
		end
	elseif scancode == 'b' then
		for i = 1, 10000 do
			tryCreateNewZ ()
		end
	elseif scancode == 'q' then
		lines = {}
		createFirstZ ()
	end

end
Screenshot 2024-01-23 170823.png
Screenshot 2024-01-23 170823.png (32.96 KiB) Viewed 3593 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
glitchapp
Party member
Posts: 237
Joined: Tue Oct 05, 2021 10:34 am
Contact:

Re: Code Doodles!

Post by glitchapp »

beautiful! a pity there is not a path to the exit, a nice procedural generated maze based game could be made out of it...
Trystan
Prole
Posts: 14
Joined: Fri Nov 24, 2023 9:30 am

Re: Code Doodles!

Post by Trystan »

I was toying around with mazes the other day and found the recursive backtracker to be pretty easy to implement and generally nice to look at. It guarantees a maze where every spot is filled in and all spots are on a single network.

f1 to generates a new maze.
RecursiveBacktracker.png
RecursiveBacktracker.png (8.76 KiB) Viewed 2583 times

Code: Select all

function love.load()
    love.window.setTitle("Recursive Backtracking Maze")
    math.randomseed(os.time())
    math.random(); math.random(); math.random()
    -- set map width and height, should be odd numbers
    mapWidth = 131
    mapHeight = 81
    tileSize = 8
    -- resize window to maze
    love.window.setMode((mapWidth+2)*tileSize,(mapHeight+2)*tileSize)
    generateMap()
end

function love.keypressed(key)
    if key=="f1" then
        generateMap()
    end
end

function generateMap()
    -- make a map full of walls
    map = {}
    for x = 1, mapWidth do
        map[x] = {}
        for y = 1, mapHeight do
            map[x][y] = 0
        end
    end
    -- make the maze
    generateMaze()
end

function generateMaze()
    -- make maze using the recursive backtracker algorithm
    -- set and dig the starting space
    local sx, sy = math.random(1,math.floor(mapWidth/2)) * 2, math.random(1, math.floor(mapHeight/2)) * 2
    stack = {{sx, sy}}
    map[sx][sy] = 1
    local neighbours, tx, ty
    while #stack > 0 do
        tx, ty = stack[#stack][1], stack[#stack][2]
        neighbours = getEligibleNeighbours(tx, ty)
        if #neighbours > 0 then
            -- we've got moves, take the first and dig!
            -- first the intermediate space
            tx = tx + neighbours[1][1]
            ty = ty + neighbours[1][2]
            map[tx][ty] = 1
            -- then the destination space
            tx = tx + neighbours[1][1]
            ty = ty + neighbours[1][2]
            map[tx][ty] = 1
            -- add the tile to the end of the stack
            table.insert(stack, {tx, ty})
        else
            -- no moves, remove the last entry on the stack
            table.remove(stack)
        end
    end
end

function getEligibleNeighbours(x, y)
    -- gets if a space two spaces away is legal and if it's a wall
    -- if both adds a dx, dy to neighbours (only +/-1 as we'll use it twice when we dig)
    local neighbours = {}
    -- north
    if y > 2 then
        if map[x][y-2] == 0 then
            table.insert(neighbours, {0, -1})
        end
    end
    -- east
    if x < mapWidth - 1 then
        if map[x+2][y] == 0 then
            table.insert(neighbours, {1, 0})
        end
    end
    -- south
    if y < mapHeight - 1 then
        if map[x][y+2] == 0 then
            table.insert(neighbours, {0, 1})
        end
    end
    -- west
    if x > 2 then
        if map[x-2][y] == 0 then
            table.insert(neighbours, {-1, 0})
        end
    end
    -- shuffle the array if we have more than one elgible
    -- five times should be fine we're only going to have 2 or 3 entries at most
    if #neighbours > 1 then
        shuffleArray(neighbours, 5)
    end
    return neighbours
end

-- quick and dirty array shuffle
function shuffleArray(array, num)
    local s1, s2
    for _ = 1, num do
        s1 = math.random(#array)
        s2 = math.random(#array)
        array[s1], array[s2] = array[s2], array[s1]
    end
end

function love.draw()
    for x = 1, mapWidth do
        for y = 1, mapHeight do
            if map[x][y] == 1 then
                love.graphics.setColor(0.7, 0.7, 0.7)
            else
                love.graphics.setColor(0.2, 0.2, 0.2)
            end
            love.graphics.rectangle("fill", x * tileSize, y * tileSize, tileSize, tileSize)
        end
    end
end
Edit: Just for fun I made a version that shows the process of creating the maze, instead of just drawing it all at once.

Code: Select all

function love.load()
    love.window.setTitle("Recursive Backtracking Maze")
    math.randomseed(os.time())
    math.random(); math.random(); math.random()
    -- set map width and height, should be odd numbers
    mapWidth = 81
    mapHeight = 61
    tileSize = 8
    love.window.setMode((mapWidth+2)*tileSize,(mapHeight+2)*tileSize)
    generateMap()
end

function love.keypressed(key)
    if key=="f1" then
        generateMap()
    end
end

function generateMap()
    -- make a map full of walls
    finished = false
    map = {}
    for x = 1, mapWidth do
        map[x] = {}
        for y = 1, mapHeight do
            map[x][y] = 0
        end
    end
    -- set and dig the starting space
    cx, cy = math.random(1,math.floor(mapWidth/2)) * 2, math.random(1, math.floor(mapHeight/2)) * 2
    stack = {{cx, cy}}
    map[cx][cy] = 1
end

function love.update()
    if finished == false then
        generateMaze()
    end
end

function generateMaze()
    -- make maze using the recursive backtracker algorithm
    local neighbours, tx, ty
    cx, cy = stack[#stack][1], stack[#stack][2]
    neighbours = getEligibleNeighbours(cx, cy)
    if #neighbours > 0 then
        -- we've got moves, take the first and dig!
        -- first the intermediate space
        cx = cx + neighbours[1][1]
        cy = cy + neighbours[1][2]
        map[cx][cy] = 1
        -- then the destination space
        cx = cx + neighbours[1][1]
        cy = cy + neighbours[1][2]
        map[cx][cy] = 1
        -- add the tile to the end of the stack
        cx, cy = cx, cy
        table.insert(stack, {cx, cy})
    else
        -- no moves, remove the last entry on the stack
        table.remove(stack)
        if #stack == 0 then
            finished = true
            cx, cy = -1, -1
        else
            cx, cy = stack[#stack][1], stack[#stack][2]
        end
    end
end

function getEligibleNeighbours(x, y)
    -- gets if a space two spaces away is legal and if it's a wall
    -- if both adds a dx, dy to neighbours (only +/-1 as we'll use it twice when we dig)
    local neighbours = {}
    -- north
    if y > 2 then
        if map[x][y-2] == 0 then
            table.insert(neighbours, {0, -1})
        end
    end
    -- east
    if x < mapWidth - 1 then
        if map[x+2][y] == 0 then
            table.insert(neighbours, {1, 0})
        end
    end
    -- south
    if y < mapHeight - 1 then
        if map[x][y+2] == 0 then
            table.insert(neighbours, {0, 1})
        end
    end
    -- west
    if x > 2 then
        if map[x-2][y] == 0 then
            table.insert(neighbours, {-1, 0})
        end
    end
    -- shuffle the array if we have more than one elgible
    -- five times should be fine we're only going to have 2 or 3 entries at most
    if #neighbours > 1 then
        shuffleArray(neighbours, 5)
    end
    return neighbours
end

-- quick and dirty array shuffle
function shuffleArray(array, num)
    local s1, s2
    for _ = 1, num do
        s1 = math.random(#array)
        s2 = math.random(#array)
        array[s1], array[s2] = array[s2], array[s1]
    end
end

function love.draw()
    for x = 1, mapWidth do
        for y = 1, mapHeight do
            if x == cx and y == cy then
                love.graphics.setColor(0.7, 0.2, 0.2)
            elseif map[x][y] == 1 then
                love.graphics.setColor(0.7, 0.7, 0.7)
            else
                love.graphics.setColor(0.2, 0.2, 0.2)
            end
            love.graphics.rectangle("fill", x * tileSize, y * tileSize, tileSize, tileSize)
        end
    end
end
User avatar
dusoft
Party member
Posts: 510
Joined: Fri Nov 08, 2013 12:07 am
Location: Europe usually
Contact:

Re: Code Doodles!

Post by dusoft »

Good job! The maze creator works well and is something to look at.
User avatar
pgimeno
Party member
Posts: 3550
Joined: Sun Oct 18, 2015 2:58 pm

Re: Code Doodles!

Post by pgimeno »

Trystan wrote: Mon Feb 12, 2024 2:58 pm I was toying around with mazes the other day and found the recursive backtracker to be pretty easy to implement and generally nice to look at. It guarantees a maze where every spot is filled in and all spots are on a single network.
There's a thread expressly dedicated to mazes:

viewtopic.php?t=19873

In fact I recently wrote an implementation of Kruskal's algorithm and was going to post it there.
glitchapp
Party member
Posts: 237
Joined: Tue Oct 05, 2021 10:34 am
Contact:

Re: Code Doodles!

Post by glitchapp »

Trystan wrote: Mon Feb 12, 2024 2:58 pm I was toying around with mazes the other day and found the recursive backtracker to be pretty easy to implement and generally nice to look at. It guarantees a maze where every spot is filled in and all spots are on a single network.

f1 to generates a new maze.

RecursiveBacktracker.png

Code: Select all

function love.load()
    love.window.setTitle("Recursive Backtracking Maze")
    math.randomseed(os.time())
    math.random(); math.random(); math.random()
    -- set map width and height, should be odd numbers
    mapWidth = 131
    mapHeight = 81
    tileSize = 8
    -- resize window to maze
    love.window.setMode((mapWidth+2)*tileSize,(mapHeight+2)*tileSize)
    generateMap()
end

function love.keypressed(key)
    if key=="f1" then
        generateMap()
    end
end

function generateMap()
    -- make a map full of walls
    map = {}
    for x = 1, mapWidth do
        map[x] = {}
        for y = 1, mapHeight do
            map[x][y] = 0
        end
    end
    -- make the maze
    generateMaze()
end

function generateMaze()
    -- make maze using the recursive backtracker algorithm
    -- set and dig the starting space
    local sx, sy = math.random(1,math.floor(mapWidth/2)) * 2, math.random(1, math.floor(mapHeight/2)) * 2
    stack = {{sx, sy}}
    map[sx][sy] = 1
    local neighbours, tx, ty
    while #stack > 0 do
        tx, ty = stack[#stack][1], stack[#stack][2]
        neighbours = getEligibleNeighbours(tx, ty)
        if #neighbours > 0 then
            -- we've got moves, take the first and dig!
            -- first the intermediate space
            tx = tx + neighbours[1][1]
            ty = ty + neighbours[1][2]
            map[tx][ty] = 1
            -- then the destination space
            tx = tx + neighbours[1][1]
            ty = ty + neighbours[1][2]
            map[tx][ty] = 1
            -- add the tile to the end of the stack
            table.insert(stack, {tx, ty})
        else
            -- no moves, remove the last entry on the stack
            table.remove(stack)
        end
    end
end

function getEligibleNeighbours(x, y)
    -- gets if a space two spaces away is legal and if it's a wall
    -- if both adds a dx, dy to neighbours (only +/-1 as we'll use it twice when we dig)
    local neighbours = {}
    -- north
    if y > 2 then
        if map[x][y-2] == 0 then
            table.insert(neighbours, {0, -1})
        end
    end
    -- east
    if x < mapWidth - 1 then
        if map[x+2][y] == 0 then
            table.insert(neighbours, {1, 0})
        end
    end
    -- south
    if y < mapHeight - 1 then
        if map[x][y+2] == 0 then
            table.insert(neighbours, {0, 1})
        end
    end
    -- west
    if x > 2 then
        if map[x-2][y] == 0 then
            table.insert(neighbours, {-1, 0})
        end
    end
    -- shuffle the array if we have more than one elgible
    -- five times should be fine we're only going to have 2 or 3 entries at most
    if #neighbours > 1 then
        shuffleArray(neighbours, 5)
    end
    return neighbours
end

-- quick and dirty array shuffle
function shuffleArray(array, num)
    local s1, s2
    for _ = 1, num do
        s1 = math.random(#array)
        s2 = math.random(#array)
        array[s1], array[s2] = array[s2], array[s1]
    end
end

function love.draw()
    for x = 1, mapWidth do
        for y = 1, mapHeight do
            if map[x][y] == 1 then
                love.graphics.setColor(0.7, 0.7, 0.7)
            else
                love.graphics.setColor(0.2, 0.2, 0.2)
            end
            love.graphics.rectangle("fill", x * tileSize, y * tileSize, tileSize, tileSize)
        end
    end
end
Edit: Just for fun I made a version that shows the process of creating the maze, instead of just drawing it all at once.

Code: Select all

function love.load()
    love.window.setTitle("Recursive Backtracking Maze")
    math.randomseed(os.time())
    math.random(); math.random(); math.random()
    -- set map width and height, should be odd numbers
    mapWidth = 81
    mapHeight = 61
    tileSize = 8
    love.window.setMode((mapWidth+2)*tileSize,(mapHeight+2)*tileSize)
    generateMap()
end

function love.keypressed(key)
    if key=="f1" then
        generateMap()
    end
end

function generateMap()
    -- make a map full of walls
    finished = false
    map = {}
    for x = 1, mapWidth do
        map[x] = {}
        for y = 1, mapHeight do
            map[x][y] = 0
        end
    end
    -- set and dig the starting space
    cx, cy = math.random(1,math.floor(mapWidth/2)) * 2, math.random(1, math.floor(mapHeight/2)) * 2
    stack = {{cx, cy}}
    map[cx][cy] = 1
end

function love.update()
    if finished == false then
        generateMaze()
    end
end

function generateMaze()
    -- make maze using the recursive backtracker algorithm
    local neighbours, tx, ty
    cx, cy = stack[#stack][1], stack[#stack][2]
    neighbours = getEligibleNeighbours(cx, cy)
    if #neighbours > 0 then
        -- we've got moves, take the first and dig!
        -- first the intermediate space
        cx = cx + neighbours[1][1]
        cy = cy + neighbours[1][2]
        map[cx][cy] = 1
        -- then the destination space
        cx = cx + neighbours[1][1]
        cy = cy + neighbours[1][2]
        map[cx][cy] = 1
        -- add the tile to the end of the stack
        cx, cy = cx, cy
        table.insert(stack, {cx, cy})
    else
        -- no moves, remove the last entry on the stack
        table.remove(stack)
        if #stack == 0 then
            finished = true
            cx, cy = -1, -1
        else
            cx, cy = stack[#stack][1], stack[#stack][2]
        end
    end
end

function getEligibleNeighbours(x, y)
    -- gets if a space two spaces away is legal and if it's a wall
    -- if both adds a dx, dy to neighbours (only +/-1 as we'll use it twice when we dig)
    local neighbours = {}
    -- north
    if y > 2 then
        if map[x][y-2] == 0 then
            table.insert(neighbours, {0, -1})
        end
    end
    -- east
    if x < mapWidth - 1 then
        if map[x+2][y] == 0 then
            table.insert(neighbours, {1, 0})
        end
    end
    -- south
    if y < mapHeight - 1 then
        if map[x][y+2] == 0 then
            table.insert(neighbours, {0, 1})
        end
    end
    -- west
    if x > 2 then
        if map[x-2][y] == 0 then
            table.insert(neighbours, {-1, 0})
        end
    end
    -- shuffle the array if we have more than one elgible
    -- five times should be fine we're only going to have 2 or 3 entries at most
    if #neighbours > 1 then
        shuffleArray(neighbours, 5)
    end
    return neighbours
end

-- quick and dirty array shuffle
function shuffleArray(array, num)
    local s1, s2
    for _ = 1, num do
        s1 = math.random(#array)
        s2 = math.random(#array)
        array[s1], array[s2] = array[s2], array[s1]
    end
end

function love.draw()
    for x = 1, mapWidth do
        for y = 1, mapHeight do
            if x == cx and y == cy then
                love.graphics.setColor(0.7, 0.2, 0.2)
            elseif map[x][y] == 1 then
                love.graphics.setColor(0.7, 0.7, 0.7)
            else
                love.graphics.setColor(0.2, 0.2, 0.2)
            end
            love.graphics.rectangle("fill", x * tileSize, y * tileSize, tileSize, tileSize)
        end
    end
end
I love it! I though of making a game out of this and here it is: play it with gamepad(thumbstick), keys needs to be tweaked and do not work as expected.

I'm not sure if the maze is always solvable... let me know if you manage to get to the exit...
Attachments
playable-maze.love
(4.24 KiB) Downloaded 26 times
glitchapp
Party member
Posts: 237
Joined: Tue Oct 05, 2021 10:34 am
Contact:

Re: Code Doodles!

Post by glitchapp »

I added touch controls and set the maze to horizontal layout to fit on mobiles just in case anyone want to play with it on mobiles.
Attachments
screenshot.jpg
screenshot.jpg (153.26 KiB) Viewed 2205 times
Recursive-Maze-Game-v0.1.love
(107.55 KiB) Downloaded 29 times
Trystan
Prole
Posts: 14
Joined: Fri Nov 24, 2023 9:30 am

Re: Code Doodles!

Post by Trystan »

There's been L-systems earlier in the thread (viewtopic.php?p=166023#p166023) but I thought I'd add these anyway because they're kind of nice to look at.
Triforce.gif
Triforce.gif (165.88 KiB) Viewed 1465 times
To increase the generation press F2, to reset to 0 press F1.

To try a different L-system change the setup function at line 8.

Most of these are taken straight from the wikipedia page on L-systems but it is fun to play around with the rules and see what happens.

Code: Select all

-- Disable output buffer so debug messages print in real time
io.stdout:setvbuf("no")

function love.load()
    love.window.setTitle("L-Systems")
    -- set the function for initialising the system here
    -- Available: Tree1 Tree2 Tree3 DragonCurve SierpinskiArrowHead SierpinskiTriangle KochCurve
    Tree2()
    lString = startString
    length = startLength
    generation = 0
    drawShape()
end

function Tree1()
    startString = "F"
    rules = {
        F = "F[-F]F[+F][F]",
    }
    deltaAngle = math.rad(25)
    startLength = 200
    startX = 400
    startY = 550
    lengthMultiplier = 0.5
    startAngle = math.pi / 2
    name = "A Tree"
end

function Tree2()
    startString = "F"
    rules = {
        F = "F[-F[F]-F]F[+F[F]+F][F]"
    }
    deltaAngle = math.rad(25)
    startLength = 200
    startX = 400
    startY = 550
    lengthMultiplier = 0.5
    startAngle = math.pi / 2
    name = "A Second Tree"
end

function Tree3()
    startString = "X"
    rules = {
        X = "F+[[X]-X]-F[-FX]+X", -- X never draws, it's just used to control evolution
        F = "FF",
    }
    deltaAngle = math.rad(25)
    startLength = 200
    startX = 400
    startY = 550
    lengthMultiplier = 0.5
    startAngle = math.pi / 2
    name = "Another Tree"
end

function DragonCurve()
    startString = "F"
    rules = {
        F = "F+G", -- F and G are both straight lines forwards, but they evolve differently
        G = "F-G",
    }
    deltaAngle = math.rad(90)
    startLength = 400
    startX = 400
    startY = 300
    lengthMultiplier = 0.68
    startAngle = math.pi / 2
    name = "Dragon Curve"
end

function SierpinskiArrowHead()
    startString = "F"
    rules = {
        F = "G-F-G",
        G = "F+G+F",
    }
    deltaAngle = math.rad(60)
    startLength = 400
    startX = 400
    startY = 550
    lengthMultiplier = 0.5
    startAngle = math.pi / 2
    name = "Sierpinski Arrowhead Curve"
end

function SierpinskiTriangle()
    startString = "F-G-G"
    rules = {
        F = "F-G+F+G-F",
        G = "GG",
    }
    deltaAngle = math.rad(120)
    startLength = 400
    startX = 400
    startY = 550
    lengthMultiplier = 0.5
    startAngle = math.pi / 2
    name = "Sierpinski Triangle"
end

function KochCurve()
    startString = "F"
    rules = {
        F = "F+F-F-F+F",
    }
    deltaAngle = math.rad(90)
    startLength = 780
    startX = 10
    startY = 590
    lengthMultiplier = 1/3
    startAngle = 0
    name = "Koch Curve"
end

function drawShape()
    if drawCanvas then
        drawCanvas:release()
    end
    drawCanvas = love.graphics.newCanvas(love.graphics.getWidth(), love.graphics.getHeight())
    love.graphics.setCanvas(drawCanvas)
    x = startX
    y = startY
    angle = startAngle
    penStack = {}
    for c = 1, lString:len() do
        drawPart(lString:sub(c, c))
    end
    love.graphics.setCanvas()
end

function drawPart(char)
    if char == "F" or char == "G" then
        -- draw line (and move x, y to endpoint)
        local nx, ny
        nx = x + math.cos(angle) * length
        ny = y - math.sin(angle) * length
        love.graphics.line(x, y, nx, ny)
        x, y = nx, ny
    elseif char == "-" then
        -- turn left
        angle = angle - deltaAngle
    elseif char == "+" then
        -- turn right
        angle = angle + deltaAngle
    elseif char == "[" then
        -- push
        table.insert(penStack, {x, y, angle})
    elseif char == "]" then
        -- pop
        x = penStack[#penStack][1]
        y = penStack[#penStack][2]
        angle = penStack[#penStack][3]
        table.remove(penStack)
    end
end

function love.keypressed(key)
    if key == "f1" then
        lString = startString
        length = startLength
        generation = 0
        drawShape()
    end
    if key == "f2" then
        lString = addGen(lString)
        length = length * lengthMultiplier
        generation = generation + 1
        drawShape()
    end
end

function addGen(oldString)
    local newString = ""
    local char, match
    for c = 1, oldString:len() do
        match = false
        char = lString:sub(c, c)
        for i, v in pairs(rules) do
            if char == i then
                newString = newString .. v
                match = true
                break
            end
        end
        if match == false then
            newString = newString .. char
        end
    end
    return newString
end

function love.update(dt)

end

function love.draw()
    if drawCanvas then
        love.graphics.draw(drawCanvas)
    end
    -- love.graphics.print(lString, 5, 5)
    love.graphics.print(name .. " - Generation: " .. generation .. ", Elements: " .. lString:len(), 5, 5)
end
Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 52 guests