## Small Useful Functions

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

### Small Useful Functions

(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 Blog

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

### Re: Small extra functions

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

Besides that, I think this is a good idea!

HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

### Re: Small extra functions

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 Blog

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

### Re: Small extra functions

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.

HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

### Re: Small extra functions

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 Blog

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

### Re: Small extra functions

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.

HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

### Re: Small extra functions

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 Blog

HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

### Re: Small extra functions

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 Blog

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

### Re: Small extra functions

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

HugoBDesigner
Party member
Posts: 403
Joined: Mon Feb 24, 2014 6:54 pm
Location: Above the Pocket Dimension
Contact:

### Re: Small extra functions

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