Page 1 of 13

### Small Useful Functions

Posted: 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
local colors = temp
local textdist = ""
for i = 1, #texts do
love.graphics.setColor(unpack(colors[i]))
args = texts[i]
args = temp+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) == "userdata" then
love.graphics.drawq(unpack(args))
else
love.graphics.rdraw(unpack(args))
end
end
``````

### Re: Small extra functions

Posted: 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!

### Re: Small extra functions

Posted: 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
local day = t
local year = t

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)
local d1 = tonumber(at)
local y1 = tonumber(at)

local m2 = tonumber(bt)
local d2 = tonumber(bt)
local y2 = tonumber(bt)

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)
local day = tonumber(t)
local year = tonumber(t)
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)
local day = tonumber(t)
local year = tonumber(t)
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 ### Re: Small extra functions

Posted: 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.

### Re: Small extra functions

Posted: 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
``````

### Re: Small extra functions

Posted: 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.

### Re: Small extra functions

Posted: 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 ### Re: Small extra functions

Posted: 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
``````

### Re: Small extra functions

Posted: Thu Apr 10, 2014 12:54 am
Neat functions. You should put them on github somehow, like maybe as a gist.

### Re: Small extra functions

Posted: 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 