Small Useful Functions

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Somewhere in Brazil
Contact:

Small Useful Functions

Post by HugoBDesigner » Mon Mar 31, 2014 2:21 pm

(Just renamed the thread to make it more "accurate")
So, I was going to ask help with a small project that uses rectangles with rounded corners. I fixed them, and now I want to share it with you guys. To make it so I haven't made a whole new thread just for it, I'm going to post also a function to print a text in multiple colors:

Rounded rectangles:
love.graphics.roundrectangle([wiki]DrawMode[/wiki], x, y, width, height, radius, segments)

Code: Select all

function love.graphics.roundrectangle(mode, x, y, w, h, rd, s)
	local r, g, b, a = love.graphics.getColor()
	local rd = rd or math.min(w, h)/4
	local s = s or 32
	local l = love.graphics.getLineWidth()
	
	local corner = 1
	local function mystencil()
		love.graphics.setColor(255, 255, 255, 255)
		if corner == 1 then
			love.graphics.rectangle("fill", x-l, y-l, rd+l, rd+l)
		elseif corner == 2 then
			love.graphics.rectangle("fill", x+w-rd+l, y-l, rd+l, rd+l)
		elseif corner == 3 then
			love.graphics.rectangle("fill", x-l, y+h-rd+l, rd+l, rd+l)
		elseif corner == 4 then
			love.graphics.rectangle("fill", x+w-rd+l, y+h-rd+l, rd+l, rd+l)
		elseif corner == 0 then
			love.graphics.rectangle("fill", x+rd, y-l, w-2*rd+l, h+2*l)
			love.graphics.rectangle("fill", x-l, y+rd, w+2*l, h-2*rd+l)
		end
	end
	
	love.graphics.setStencil(mystencil)
	love.graphics.setColor(r, g, b, a)
	love.graphics.circle(mode, x+rd, y+rd, rd, s)
	love.graphics.setStencil()
	corner = 2
	love.graphics.setStencil(mystencil)
	love.graphics.setColor(r, g, b, a)
	love.graphics.circle(mode, x+w-rd, y+rd, rd, s)
	love.graphics.setStencil()
	corner = 3
	love.graphics.setStencil(mystencil)
	love.graphics.setColor(r, g, b, a)
	love.graphics.circle(mode, x+rd, y+h-rd, rd, s)
	love.graphics.setStencil()
	corner = 4
	love.graphics.setStencil(mystencil)
	love.graphics.setColor(r, g, b, a)
	love.graphics.circle(mode, x+w-rd, y+h-rd, rd, s)
	love.graphics.setStencil()
	corner = 0
	love.graphics.setStencil(mystencil)
	love.graphics.setColor(r, g, b, a)
	love.graphics.rectangle(mode, x, y, w, h)
	love.graphics.setStencil()
end
Multi-printing texts:
love.graphics.multiprint(textsTable, colorsTable, x, y, r, sx, sy, ox, oy, kx, ky)

Code: Select all

function love.graphics.multiprint( ... )
	local r, g, b, a = love.graphics.getColor()
	local args = { ... }
	local temp = { ... }
	table.remove(args, 1)
	local texts = temp[1]
	local colors = temp[2]
	local textdist = ""
	for i = 1, #texts do
		love.graphics.setColor(unpack(colors[i]))
		args[1] = texts[i]
		args[2] = temp[3]+love.graphics.getFont():getWidth(textdist)
		love.graphics.print(unpack(args))
		textdist = textdist .. texts[i]
	end
	love.graphics.setColor(r, g, b, a)
end
Should I post more of these? May I make this a thread where you can post these kind of useful, small functions? Is there already a thread for it (not projects, but small, useful functions)? Thank you!

Oh, and, also, a small function to make LÖVE 0.8.0 and LÖVE 0.9.0 compatible in love.graphics.draw:

Code: Select all

love.graphics.rdraw = love.graphics.draw
love.graphics.drawq = love.graphics.drawq or love.graphics.rdraw
function love.graphics.draw( ... )
	local args = { ... }
	if type(args[2]) == "userdata" then
		love.graphics.drawq(unpack(args))
	else
		love.graphics.rdraw(unpack(args))
	end
end
Last edited by HugoBDesigner on Wed May 21, 2014 4:47 pm, edited 1 time in total.
HugoBDesigner - my Twitter
HugoBDesigner - my Blog

Rehnrald
Prole
Posts: 29
Joined: Fri Jul 19, 2013 6:11 am

Re: Small extra functions

Post by Rehnrald » Mon Mar 31, 2014 8:52 pm

I think this would go in General, not Support and Development.

Besides that, I think this is a good idea!

User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Somewhere in Brazil
Contact:

Re: Small extra functions

Post by HugoBDesigner » Thu Apr 03, 2014 12:10 am

Thank you very much! Could some moderator please move this thread to there then?

EDIT: A few more small-but-really-useful functions:

table.size(table)
Returns the table size. Works better than #table (because this one counts strings too, not only integers)

Code: Select all

function table.size(t)
	local s = 0
	for i, v in pairs(t) do
		s = s + 1
	end
	return s
end
table.contains(table, entry)
Returns the table position if it finds the entry, false otherwise.

Code: Select all

function table.contains(t, e)
	for i, v in pairs(t) do
		if v == e then
			return i
		end
	end
	return false
end
table.copy(table, copyInsideTables)
Returns an exact equal copy of a table. Optionally, the tables inside it too.

Code: Select all

function table.copy(t, s) --s for tables inside table
	local s = s or false
	if not t then
		return {}
	end
	local returner = {}
	for i, v in pairs(t) do
		if s and type(v) == "table" then
			returner[i] = table.copy(v, true)
		else
			returner[i] = v
		end
	end
	return returner
end
round(number, decimal values)
Much like the well-known round function, I just made it simpler...

Code: Select all

function round(n, r)
	local r = r or 0
	return math.floor(n*10^r)/10^r
end
toboolean(string)
Pretty obvious...

Code: Select all

function toboolean(s)
	if s == "true" then
		return true
	elseif s == "false" then
		return false
	elseif s == "nil" then
		return nil
	else
		error("Attempted to convert " .. s .. " to boolean")
	end
end
isLeapYear(year)
Returns true if it is a leap year, and false otherwise. Only works for Gregorian calendar, I think.

Code: Select all

function isLeapYear(y)
	if math.mod(y, 400) == 0 then
		return true
	elseif math.mod(y, 4) == 0 and math.mod(y, 100) ~= 0 then
		return true
	end
	return false
end
validDay(month, day, year)
Returns true if the date exists, false otherwise. Requires "isLeapYear".

Code: Select all

function validDay(m, d, y)
	local mon = m
	local day = d
	local year = y
	
	local maxmon = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
	
	local can = true
	if type(day) ~= "number" or type(mon) ~= "number" or type(year) ~= "number" then
		can = false
	elseif mon == 2 and day == 29 and isLeapYear(year) == false then
		can = false
	elseif day > maxmon[mon] then
		can = false
	elseif day < 1 or mon < 1 or year < 1800 then --before 1800 is completely unneeded
		can = false
	elseif mon > 12 or year > 3000 then --after it? Who will use this after it?
		can = false
	elseif math.mod(day, 1) ~= 0 or math.mod(mon, 1) ~= 0 or math.mod(year, 1) ~= 0 then
		can = false
	end
	
	return can
end
weekDay(day string)
As it says, it gets the week day of a date (or false if fails). Day strings are formatted like "mm/dd/yyyy". Requires "validDay" function.

Code: Select all

function weekDay(s, a)
	local week = {"sun", "mon", "tue", "wed", "thu", "fri", "sat"}
	local weeki = {"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"}
	local t = s:split("/")
	for i = 1, 3 do
		t[i] = tonumber(t[i]) or false
	end
	local mon = t[1]
	local day = t[2]
	local year = t[3]
	
	local can = validDay(mon, day, year)
	
	if can then
		if a then
			return weeki[os.date("%w", os.time{day=day, month=mon, year=year})+1]
		else
			return week[os.date("%w", os.time{day=day, month=mon, year=year})+1]
		end
	else
		print("Error: " .. s .. " is not a valid day")
		return false
	end
end
before(date1, date2)
Returns true if date1 comes BEFORE date2. Dates are formatted like "mm/dd/yyyy".

Code: Select all

function before(a, b)
	local at = a:split("/")
	local bt = b:split("/")
	
	local m1 = tonumber(at[1])
	local d1 = tonumber(at[2])
	local y1 = tonumber(at[3])
	
	local m2 = tonumber(bt[1])
	local d2 = tonumber(bt[2])
	local y2 = tonumber(bt[3])
	
	if d1 < d2 and m1 <= m2 and y1 <= y2 then
		return true
	elseif d1 >= d2 and m1 < m2 and y1 <= y2 then
		return true
	elseif d1 >= d2 and y1 < y2 then
		return true
	elseif y1 < y2 then
		return true
	end
	return false
end
after(date1, date2)
Returns true if date1 comes AFTER date2. Dates are formatted like "mm/dd/yyyy". Requires "before".

Code: Select all

function after(a, b)
	if a == b then
		return false
	else
		return not before(b, a)
	end
end
string:fill(size, position, character)
Fills the string with the specified character until it gets from the same size specified. An example: "12":fill(5, "start", "0") returns "00012". Not sure if this one requires "class.lua" because of "self" things...

Code: Select all

function string:fill(sz, pos, ch) --Inspired by Maurice, re-made by me
	local n = self
	local ch = ch or "0"
	local pos = pos or "start"
	if string.len(n) >= sz then
		return n
	else
		if pos == "before" or pos == "start" or pos == "beginning" then
			while string.len(n) < sz do
				n = ch .. n
			end
		elseif pos == "after" or pos = "end" or pos == "ending" then
			while string.len(n) < sz do
				n = n .. ch
			end
		end
	end
	
	return n
end
lastDay(date)
Returns the day that came before "date", or empty if fails. Dates are formatted like "mm/dd/yyyy". Requires "string:fill", "validDay" and "isLeapYear".

Code: Select all

function lastDay(s)
	local t = s:split("/")
	local mon = tonumber(t[1])
	local day = tonumber(t[2])
	local year = tonumber(t[3])
	if validDay(mon, day, year) == false then
		return ""
	end
	
	local maxmon = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
	if day == 1 then
		if mon == 1 then
			if year == 1800 then
				return ""
			else
				year = year - 1
				mon = 12
				day = maxmon[mon]
			end
		else
			mon = mon - 1
			day = maxmon[mon]
			if mon == 2 and isLeapYear(year) then
				day = 29
			end
		end
	else
		day = day - 1
	end
	
	local nd = tostring(day)
	local nm = tostring(mon)
	local ny = tostring(year)
	
	return nm:fill(2) .. "/" .. nd:fill(2) .. "/" .. ny
end
nextDay(date)
Returns the day that came after "date", or empty if fails. Dates are formatted like "mm/dd/yyyy". Requires "string:fill", "validDay" and "isLeapYear".

Code: Select all

function nextDay(s)
	local t = s:split("/")
	local mon = tonumber(t[1])
	local day = tonumber(t[2])
	local year = tonumber(t[3])
	if validDay(mon, day, year) == false then
		return ""
	end
	
	local maxmon = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
	if dia == maxmon[mon] and not (day == 28 and mon == 2 and isLeapYear(year)) then
		if mon == 12 then
			if year == 3000 then
				return ""
			else
				year = year + 1
				mon = 1
				day = 1
			end
		else
			mon = mon + 1
			day = 1
		end
	else
		day = day + 1
	end
	
	local nd = tostring(day)
	local nm = tostring(mon)
	local ny = tostring(year)
	
	return nm:fill(2) .. "/" .. nd:fill(2) .. "/" .. ny
end




And, once again, just so it gets more useful and less out-of-place, could some moderator please move this to "General"? It fits better there, and I don't want to make two threads for it, it's a waste of space... Thank you ;)
Last edited by HugoBDesigner on Thu Apr 03, 2014 11:37 am, edited 2 times in total.
HugoBDesigner - my Twitter
HugoBDesigner - my Blog

User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: Small extra functions

Post by Robin » Thu Apr 03, 2014 9:04 am

table.copy(t, true) will get stuck in an infinite loop (actually, it'll cause a stack overflow rather quickly) if t contains a cycle.
Help us help you: attach a .love.

User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Somewhere in Brazil
Contact:

Re: Small extra functions

Post by HugoBDesigner » Thu Apr 03, 2014 11:23 am

Does this solve the problem or will I have to make something much more complicated?

Code: Select all

function table.copy(t, s) --s for tables inside table
   local s = s or false
   if not t then
      return {}
   end
   local returner = {}
   for i, v in pairs(t) do
      if s and type(v) == "table" then
         if v ~= t then
             returner[i] = table.copy(v, true)
         else
             returner[i] = table.copy(t, false)
         end
      else
         returner[i] = v
      end
   end
   return returner
end
HugoBDesigner - my Twitter
HugoBDesigner - my Blog

User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: Small extra functions

Post by Robin » Thu Apr 03, 2014 3:36 pm

Nope, that only does something in a very specific circumstance: if one of the tables copied directly contains itself as a value.

This is a correctly-functioning implementation of a deep copy:

Code: Select all

function deepcopy(t, cache)
    if type(t) ~= 'table' then
        return t
    end

    cache = cache or {}
    if cache[t] then
        return cache[t]
    end

    local new = {}

    cache[t] = new

    for key, value in pairs(t) do
        new[deepcopy(key, cache)] = deepcopy(value, cache)
    end

    return new
end
You could modify it to accept an extra Boolean argument to choose between shallow and deep copying, but kikito will yell at you if you use flag arguments, so it might be better to just keep two functions: one for deep copies, one for shallow copies.
Help us help you: attach a .love.

User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Somewhere in Brazil
Contact:

Re: Small extra functions

Post by HugoBDesigner » Thu Apr 03, 2014 4:17 pm

Oh, OK. I see. Thank you, then! I might keep using my function because I'm not dealing with anything too complicated (or tables that contains themselves), but your function seems a lot more helpful in advanced stuff :)
HugoBDesigner - my Twitter
HugoBDesigner - my Blog

User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Somewhere in Brazil
Contact:

Re: Small extra functions

Post by HugoBDesigner » Wed Apr 09, 2014 4:48 pm

Just made another small function related to round rectangles. I needed to make a kind of "setScissor" in that format for a loading bar, so I used this:

The arguments are just like the round rectangle. A warning: I couldn't get the corner circles cropped, so if your round rectangle corner diameter is smaller than half of the width/height of your rectangle, it may look weird. But making a round rectangle with corners bigger than the sides is already weird enough by itself, right?

Code: Select all

function love.graphics.roundScissor(x, y, w, h, r, s)
	local r = r or false
	if w and h and not r then
		r = math.min(w, h)/4
	end
	local s = s or 32
	local cr, cg, cb, ca = love.graphics.getColor()
	
	local function myStencil()
		if x and y and w and h then
			love.graphics.setColor(255, 255, 255, 255)
			love.graphics.circle("fill", x+r, y+r, r, s)
			love.graphics.circle("fill", x+w-r, y+r, r, s)
			love.graphics.circle("fill", x+r, y+h-r, r, s)
			love.graphics.circle("fill", x+w-r, y+h-r, r, s)
			love.graphics.rectangle("fill", x+r, y, w-2*r, h)
			love.graphics.rectangle("fill", x, y+r, w, h-2*r)
		end
	end
	
	if x and y and w and h then
		love.graphics.setStencil(myStencil)
	else
		love.graphics.setStencil()
	end
	love.graphics.setColor(cr, cg, cb, ca)
end
HugoBDesigner - my Twitter
HugoBDesigner - my Blog

User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: Small extra functions

Post by Inny » Thu Apr 10, 2014 12:54 am

Neat functions. You should put them on github somehow, like maybe as a gist.

User avatar
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Somewhere in Brazil
Contact:

Re: Small extra functions

Post by HugoBDesigner » Thu Apr 10, 2014 1:17 am

Inny wrote:Neat functions. You should put them on github somehow, like maybe as a gist.
I'm making a "kind" of library with all these functions, plus a few GUI elements, such as inputs, buttons, scroll bars and lists. I might share them as soon as I finish it :)
HugoBDesigner - my Twitter
HugoBDesigner - my Blog

Post Reply

Who is online

Users browsing this forum: No registered users and 13 guests