Skip to content

Instantly share code, notes, and snippets.

@reefwing
Created September 30, 2012 00:44
Show Gist options
  • Save reefwing/3805511 to your computer and use it in GitHub Desktop.
Save reefwing/3805511 to your computer and use it in GitHub Desktop.
Tutorial 18 - Saving and Loading Complicated Tables
--# Button
Button = class()
function Button:init(x, y, pressed, text)
-- Initialise Button
self.x = x or 0
self.y = y or 0
self.pressed = pressed or false
self.text = text or ""
self.normalImage = readImage("Dropbox:Button 64x64px")
self.pressedImage = readImage("Dropbox:pressedButton 64x64px")
self.w = self.normalImage.width
self.h = self.normalImage.height
self.tag = nil
self.action = nil
end
function Button:draw()
-- Codea does not automatically call this method
pushStyle()
if self.pressed then
sprite(self.pressedImage, self.x, self.y)
fill(whiteColor)
else
sprite(self.normalImage, self.x, self.y)
fill(blackColor)
end
font("ArialRoundedMTBold")
fontSize(22)
textMode(CENTER)
text(self.text, self.x, self.y)
popStyle()
end
-- Touch Handling
function Button:hit(p)
-- Was the touch on this button?
-- Note code repurposed from the original button class
-- provide in the Codea examples.
local l = self.x - self.w/2
local r = self.x + self.w/2
local t = self.y + self.h/2
local b = self.y - self.h/2
if p.x > l and p.x < r and p.y > b and p.y < t then
return true
else
return false
end
end
function Button:touched(touch)
-- Codea does not automatically call this method
if touch.state == ENDED and self:hit(vec2(touch.x, touch.y)) then
self.pressed = not self.pressed
if self.action then
self.action()
end
end
end
--# Cell
Cell = class()
-- dGenerator Cell Class
-- Reefwing Software
--
-- Version 1.2 (Modified from MineSweeper Cell Class)
--
-- Each element in the dGenerator two dimensional grid{}
-- consists of a cell object. Each cell is responsible for
-- tracking its own state and drawing the appropriate sprite
-- based on this state.
--
-- States available are: Obstacle, Clear, Start or Finish.
-- There can only be one start and one finish cell in the
-- matrix.
function Cell:init(i, j, state, x, y)
-- Cell Initialisation.
self.index = vec2(i, j) -- location of cell within the grid{} table
self.state = state -- contents of cell
self.pos = vec2(x, y) -- position of cell on the screen
self.action = nil -- call back function when cell tapped
self.size = vec2(32, 32) -- size of cell on screen
self.showGrid = false -- if true a border will be drawn around the cell
end
-- Cell Data Export and Import Functions (for saving and retrieving data)
function Cell:dataToString()
return self.state
end
function Cell:dataFromString(str)
self.state = str
end
-- Cell Draw Functions
function Cell:draw()
-- Codea does not automatically call this method
-- Draw the appropriate cell image based on its state.
if self.state == stateObstacle then
sprite("Dropbox:obstacleSprite", self.pos.x, self.pos.y)
elseif self.state == stateClear then
sprite("Dropbox:clearSprite", self.pos.x, self.pos.y)
elseif self.state == stateStart then
sprite("Dropbox:startSprite", self.pos.x, self.pos.y)
elseif self.state == stateEnd then
sprite("Dropbox:endSprite", self.pos.x, self.pos.y)
elseif self.state == statePath then
sprite("Dropbox:pathSprite", self.pos.x, self.pos.y)
end
-- If showGrid is true we draw a border around the cell.
if self.showGrid then
pushStyle()
noSmooth()
fill(clearColor)
stroke(darkGrayColor)
strokeWidth(1)
rectMode(CENTER)
rect(self.pos.x, self.pos.y, self.size.x, self.size.y)
popStyle()
end
end
-- Cell Touch Handling
function Cell:hit(p)
-- Was the touch on this cell?
-- Note code repurposed from the original button class
-- provide in the Codea examples.
local l = self.pos.x - self.size.x/2
local r = self.pos.x + self.size.x/2
local t = self.pos.y + self.size.y/2
local b = self.pos.y - self.size.y/2
if p.x > l and p.x < r and
p.y > b and p.y < t then
return true
end
return false
end
function Cell:touched(touch)
-- Codea does not automatically call this method
if touch.state == ENDED and self:hit(vec2(touch.x,touch.y)) then
if self.action then
-- call back method called.
self.action(self.index)
end
end
end
--# Colors
-- Make available the predefined colors from UIColor
--
-- Version 1.1
blackColor = color(0, 0, 0)
darkGrayColor = color(85, 85, 85)
lightGrayColor = color(170, 170, 170)
whiteColor = color(255, 255, 255)
grayColor = color(128, 128, 128)
redColor = color(255, 0, 0)
greenColor = color(0, 255, 0)
blueColor = color(0, 0, 255)
cyanColor = color(0, 255, 255)
yellowColor = color(255, 255, 0)
magentaColor = color(255, 0, 255)
orangeColor = color(255, 128, 0)
purpleColor = color(128, 0, 128)
brownColor = color(153, 102, 51)
clearColor = color(0, 0, 0, 0)
lightTextColor = color(255, 255, 255, 153)
darkTextColor = color(0, 0, 0)
-- Reefwing specific Colours
lightRedColor = color(243, 157, 33)
lightBlueColor = color(0, 0, 255, 128)
codeaDarkBackground = color(40, 40, 50)
--# DataDumper
--[[ DataDumper.lua
Copyright (c) 2007 Olivetti-Engineering SA
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]
local dumplua_closure = [[
local closures = {}
local function closure(t)
closures[#closures+1] = t
t[1] = assert(loadstring(t[1]))
return t[1]
end
for _,t in pairs(closures) do
for i = 2,#t do
debug.setupvalue(t[1], i-1, t[i])
end
end
]]
local lua_reserved_keywords = {
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while' }
local function keys(t)
local res = {}
local oktypes = { stringstring = true, numbernumber = true }
local function cmpfct(a,b)
if oktypes[type(a)..type(b)] then
return a < b
else
return type(a) < type(b)
end
end
for k in pairs(t) do
res[#res+1] = k
end
table.sort(res, cmpfct)
return res
end
local c_functions = {}
for _,lib in pairs{'_G', 'string', 'table', 'math',
'io', 'os', 'coroutine', 'package', 'debug'} do
local t = _G[lib] or {}
lib = lib .. "."
if lib == "_G." then lib = "" end
for k,v in pairs(t) do
if type(v) == 'function' and not pcall(string.dump, v) then
c_functions[v] = lib..k
end
end
end
function DataDumper(value, varname, fastmode, ident)
local defined, dumplua = {}
-- Local variables for speed optimization
local string_format, type, string_dump, string_rep =
string.format, type, string.dump, string.rep
local tostring, pairs, table_concat =
tostring, pairs, table.concat
local keycache, strvalcache, out, closure_cnt = {}, {}, {}, 0
setmetatable(strvalcache, {__index = function(t,value)
local res = string_format('%q', value)
t[value] = res
return res
end})
local fcts = {
string = function(value) return strvalcache[value] end,
number = function(value) return value end,
boolean = function(value) return tostring(value) end,
['nil'] = function(value) return 'nil' end,
['function'] = function(value)
return string_format("loadstring(%q)", string_dump(value))
end,
userdata = function() error("Cannot dump userdata") end,
thread = function() error("Cannot dump threads") end,
}
local function test_defined(value, path)
if defined[value] then
if path:match("^getmetatable.*%)$") then
out[#out+1] = string_format("s%s, %s)\n", path:sub(2,-2), defined[value])
else
out[#out+1] = path .. " = " .. defined[value] .. "\n"
end
return true
end
defined[value] = path
end
local function make_key(t, key)
local s
if type(key) == 'string' and key:match('^[_%a][_%w]*$') then
s = key .. "="
else
s = "[" .. dumplua(key, 0) .. "]="
end
t[key] = s
return s
end
for _,k in ipairs(lua_reserved_keywords) do
keycache[k] = '["'..k..'"] = '
end
if fastmode then
fcts.table = function (value)
-- Table value
local numidx = 1
out[#out+1] = "{"
for key,val in pairs(value) do
if key == numidx then
numidx = numidx + 1
else
out[#out+1] = keycache[key]
end
local str = dumplua(val)
out[#out+1] = str..","
end
if string.sub(out[#out], -1) == "," then
out[#out] = string.sub(out[#out], 1, -2);
end
out[#out+1] = "}"
return ""
end
else
fcts.table = function (value, ident, path)
if test_defined(value, path) then return "nil" end
-- Table value
local sep, str, numidx, totallen = " ", {}, 1, 0
local meta, metastr = (debug or getfenv()).getmetatable(value)
if meta then
ident = ident + 1
metastr = dumplua(meta, ident, "getmetatable("..path..")")
totallen = totallen + #metastr + 16
end
for _,key in pairs(keys(value)) do
local val = value[key]
local s = ""
local subpath = path
if key == numidx then
subpath = subpath .. "[" .. numidx .. "]"
numidx = numidx + 1
else
s = keycache[key]
if not s:match "^%[" then subpath = subpath .. "." end
subpath = subpath .. s:gsub("%s*=%s*$","")
end
s = s .. dumplua(val, ident+1, subpath)
str[#str+1] = s
totallen = totallen + #s + 2
end
if totallen > 80 then
sep = "\n" .. string_rep(" ", ident+1)
end
str = "{"..sep..table_concat(str, ","..sep).." "..sep:sub(1,-3).."}"
if meta then
sep = sep:sub(1,-3)
return "setmetatable("..sep..str..","..sep..metastr..sep:sub(1,-3)..")"
end
return str
end
fcts['function'] = function (value, ident, path)
if test_defined(value, path) then return "nil" end
if c_functions[value] then
return c_functions[value]
elseif debug == nil or debug.getupvalue(value, 1) == nil then
return string_format("loadstring(%q)", string_dump(value))
end
closure_cnt = closure_cnt + 1
local res = {string.dump(value)}
for i = 1,math.huge do
local name, v = debug.getupvalue(value,i)
if name == nil then break end
res[i+1] = v
end
return "closure " .. dumplua(res, ident, "closures["..closure_cnt.."]")
end
end
function dumplua(value, ident, path)
return fcts[type(value)](value, ident, path)
end
if varname == nil then
varname = "return "
elseif varname:match("^[%a_][%w_]*$") then
varname = varname .. " = "
end
if fastmode then
setmetatable(keycache, {__index = make_key })
out[1] = varname
table.insert(out,dumplua(value, 0))
return table.concat(out)
else
setmetatable(keycache, {__index = make_key })
local items = {}
for i=1,10 do items[i] = '' end
items[3] = dumplua(value, ident or 0, "t")
if closure_cnt > 0 then
items[1], items[6] = dumplua_closure:match("(.*\n)\n(.*)")
out[#out+1] = ""
end
if #out > 0 then
items[2], items[4] = "local t = ", "\n"
items[5] = table.concat(out)
items[7] = varname .. "t"
else
items[2] = varname
end
return table.concat(items)
end
end
--# FileManager
-- Define Directory Types
ProjectInfo = 1
ProjectData = 2
LocalData = 3
GlobalData = 4
-- Codea FileManager v1.2
--
-- A simple file manager to examine the Codea provided
-- persistent storage. Currently handles Local Data, Project Data,
-- Project Information and Global Data.
--
-- Reefwing Software
-- 15 September 2012
--
-- This class requires the ListScroll and TextItem class written by @sanit and
-- the MenuBar class written by @dave1707 (with some modifications).
--
-- WARNING - FILE DELETION CAN NOT BE UNDONE AND THE CLASS DOES NOT ASK FOR CONFIRMATION.
-- USE WITH EXTREME CAUTION AND BACK UP YOUR DATA BEFORE USING THIS
-- FILE MANAGER CLASS.
FileManager = class()
function FileManager:init()
-- Initialise the FileManager parameters and
-- load up the currently supported data stores.
directoryList = ListScroll(vec2(25, HEIGHT - 500), 200, 400)
directoryContents = {"Project Info", "Project Data", "Local Data", "Global Data"}
for i = 1, #directoryContents do
local item = TextItem(i, directoryContents[i])
if i == 1 then
-- pre select Project Info "Directory"
item.selected = true
end
directoryList:add(item)
end
currentDirectory = ProjectInfo
dataKeyList = ListScroll(vec2(227, HEIGHT - 500), 260, 400)
-- Pre-fetch results
infoKeyTable = {"Description", "Author", "Date", "Version", "Comments"}
loadProjectKeys()
loadLocalKeys()
loadGlobalKeys()
displayKeys(infoKeyTable)
currentKey = 1
valueString = readProjectInfo(infoKeyTable[currentKey])
-- Setup the MenuBar
MenuSetup()
end
-- Functions to read keys from the available pLists
function loadProjectKeys()
projectKeyTable = listProjectData()
if #projectKeyTable == 0 then
projectKeyTable = {"nil"}
end
end
function loadLocalKeys()
localKeyTable = listLocalData()
if #localKeyTable == 0 then
localKeyTable = {"nil"}
end
end
function loadGlobalKeys()
globalKeyTable = listGlobalData()
if #globalKeyTable == 0 then
globalKeyTable = {"nil"}
end
end
-- This function displays the keys passed in keyTable within the dataKeyList ListScroll
function displayKeys(keyTable)
dataKeyList.items = {}
for i = 1, #keyTable do
local displayKey = string.truncate(keyTable[i], 14)
local item = TextItem(i, displayKey)
if i == 1 then
-- pre select first key in Project Info
item.selected = true
end
dataKeyList:add(item)
end
end
-- Setup the menu items at the top of the ListScrolls
-- Note that the call back functions are in the Main tab.
function MenuSetup()
pointer2 = nil
pointer3 = nil
b1tab = {}
local y = HEIGHT - 80
-- Create tables for main menu bar
table.insert(b1tab, MenuBar(80, y, 100, 30, "Load", loadFile))
table.insert(b1tab, MenuBar(180, y, 100, 30, "Cancel", cancelFileManager))
table.insert(b1tab, MenuBar(280, y, 100, 30, "About", aboutFileManager))
table.insert(b1tab, MenuBar(380, y, 100, 30, "Delete", deleteFile))
pointer1 = b1tab
-- Global variables to prevent touches on scroll list
-- while subMenu is visible and to update the key list
-- if the directory changes.
subMenuShown = false
directoryChanged = false
end
-- Update functions for List Scrolls
function updateDirectorySelection()
local dlsi = directoryList.selectedItem
if dlsi ~= currentDirectory then
if dlsi == nil then
-- No Items Selected assign to default
dlsi = ProjectInfo
displayKeys(infoKeyTable)
elseif dlsi == ProjectData then
displayKeys(projectKeyTable)
elseif dlsi == ProjectInfo then
displayKeys(infoKeyTable)
elseif dlsi == LocalData then
displayKeys(localKeyTable)
elseif dlsi == GlobalData then
displayKeys(globalKeyTable)
end
currentDirectory = dlsi
directoryChanged = true
end
end
function updateKeySelection()
local dksi = dataKeyList.selectedItem
if dksi ~= currentKey or directoryChanged then
if directoryChanged then
dataKeyList.selectedItem = 1
currentKey = 1
directoryChanged = false
else
currentKey = dksi
end
if currentDirectory == nil then
-- No Directory Selected
valueString = ""
elseif currentDirectory == ProjectData and projectKeyTable[currentKey] ~= nil then
valueString = readProjectData(projectKeyTable[currentKey]) or "nil"
elseif currentDirectory == ProjectInfo and infoKeyTable[currentKey] ~= nil then
valueString = readProjectInfo(infoKeyTable[currentKey]) or "nil"
elseif currentDirectory == LocalData and localKeyTable[currentKey] ~= nil then
valueString = readLocalData(localKeyTable[currentKey]) or "nil"
elseif currentDirectory == GlobalData and globalKeyTable[currentKey] ~= nil then
valueString = readGlobalData(globalKeyTable[currentKey]) or "nil"
end
valueString = string.truncate(valueString, 150)
end
end
function FileManager:draw()
-- Codea does not automatically call this method
-- Handle Directory Selection
updateDirectorySelection()
updateKeySelection()
directoryList:draw()
dataKeyList:draw()
-- Draw Menu Bar
pushStyle()
fill(0,0,255,255)
rect(25, HEIGHT - 98, 725, 36)
popStyle()
MenuBar:menu1()
-- Draw the results window
pushStyle()
textWrapWidth(250)
textMode(CORNER)
font("Courier")
fontSize(24)
fill(255)
rect(489, HEIGHT - 500, 260, 400)
fill(0)
local _,h = textSize(valueString)
text(valueString, 496, HEIGHT - 100 - h)
popStyle()
end
function FileManager:touched(touch)
-- Codea does not automatically call this method
if touch.state == ENDED then
MenuBar:check(touch)
end
if pointer2 ~= nil then
-- Sub menu visible
subMenuShown = true
elseif subMenuShown and touch.state == ENDED then
subMenuShown = false
else
-- Sub menu is not visible so handle scroll touches
directoryList:touched(touch)
dataKeyList:touched(touch)
end
end
-- String Helper
function string.truncate(str, num)
if string.len(str) > num then
str = string.sub(str,1,num) .. "..."
end
return str
end
--# FindPath
-- A* Path Finding Function
--
-- Based on the Algorithm posted at mobile.tutsplus.com
-- Corona SDK: Game Development Path Finding
--
-- Version 1.0
--
-- Modified by Reefwing Software (www.reefwing.com.au)
--
-- Modifications:
--
-- - Ported to Codea from Corona
-- - Changed isObstacle variable to a more general state variable
function CalcMoves(board, startX, startY, targetX, targetY)
local openlist = {} -- Possible Moves
local closedlist = {} -- Checked Squares
local listk = 1 -- open list counter
local closedk = 0 -- Closedlist counter
local tempH = math.abs(startX-targetX) + math.abs(startY-targetY)
local tempG = 0
openlist[1] = {x = startX, y = startY, g = 0, h = tempH, f = 0 + tempH ,par = 1}
local xsize = table.getn(board[1])
local ysize = table.getn(board)
local curSquare = {}
local curSquareIndex = 1 -- Index of current base
while listk > 0 do
local lowestF = openlist[listk].f
curSquareIndex = listk
for k = listk, 1, -1 do
if openlist[k].f < lowestF then
lowestF = openlist[k].f
curSquareIndex = k
end
end
closedk = closedk + 1
table.insert(closedlist,closedk,openlist[curSquareIndex])
curSquare = closedlist[closedk] -- define current base from which to grow list
local rightOK = true
local leftOK = true -- Booleans defining if they're OK to add
local downOK = true -- (must be reset for each while loop)
local upOK = true
-- Look through closedlist. Makes sure that the path doesn't double back
if closedk > 0 then
for k = 1, closedk do
if closedlist[k].x == curSquare.x + 1 and closedlist[k].y == curSquare.y then
rightOK = false
end
if closedlist[k].x == curSquare.x-1 and closedlist[k].y == curSquare.y then
leftOK = false
end
if closedlist[k].x == curSquare.x and closedlist[k].y == curSquare.y + 1 then
downOK = false
end
if closedlist[k].x == curSquare.x and closedlist[k].y == curSquare.y - 1 then
upOK = false
end
end
end
-- Check if next points are on the map and within moving distance
if curSquare.x + 1 > xsize then
rightOK = false
end
if curSquare.x - 1 < 1 then
leftOK = false
end
if curSquare.y + 1 > ysize then
downOK = false
end
if curSquare.y - 1 < 1 then
upOK = false
end
-- If it is on the map, check map for obstacles
-- Lua returns an error if you try to access a table position
-- that doesn't exist, so you can't combine it with above.
if curSquare.x + 1 <= xsize and
board[curSquare.x+1][curSquare.y].state == stateObstacle then
rightOK = false
end
if curSquare.x - 1 >= 1 and
board[curSquare.x-1][curSquare.y].state == stateObstacle then
leftOK = false
end
if curSquare.y + 1 <= ysize and
board[curSquare.x][curSquare.y+1].state == stateObstacle then
downOK = false
end
if curSquare.y - 1 >= 1 and
board[curSquare.x][curSquare.y-1].state == stateObstacle then
upOK = false
end
-- check if the move from the current base is shorter then from the former parent
tempG = curSquare.g + 1
for k = 1,listk do
if rightOK and openlist[k].x==curSquare.x+1 and openlist[k].y==curSquare.y
and openlist[k].g>tempG then
tempH=math.abs((curSquare.x+1)-targetX)+math.abs(curSquare.y-targetY)
table.insert(openlist,k,{x=curSquare.x+1, y=curSquare.y, g=tempG,
h=tempH, f=tempG+tempH, par=closedk})
rightOK=false
end
if leftOK and openlist[k].x==curSquare.x-1 and openlist[k].y==curSquare.y
and openlist[k].g>tempG then
tempH=math.abs((curSquare.x-1)-targetX)+math.abs(curSquare.y-targetY)
table.insert(openlist,k,{x=curSquare.x-1, y=curSquare.y, g=tempG,
h=tempH, f=tempG+tempH, par=closedk})
leftOK=false
end
if downOK and openlist[k].x==curSquare.x and openlist[k].y==curSquare.y+1
and openlist[k].g>tempG then
tempH=math.abs((curSquare.x)-targetX)+math.abs(curSquare.y+1-targetY)
table.insert(openlist,k,{x=curSquare.x, y=curSquare.y+1, g=tempG,
h=tempH, f=tempG+tempH, par=closedk})
downOK=false
end
if upOK and openlist[k].x==curSquare.x and openlist[k].y==curSquare.y-1
and openlist[k].g>tempG then
tempH=math.abs((curSquare.x)-targetX)+math.abs(curSquare.y-1-targetY)
table.insert(openlist,k,{x=curSquare.x, y=curSquare.y-1, g=tempG,
h=tempH, f=tempG+tempH, par=closedk})
upOK=false
end
end
-- Add points to openlist
-- Add point to the right of current base point
if rightOK then
listk=listk+1
tempH=math.abs((curSquare.x+1)-targetX)+math.abs(curSquare.y-targetY)
table.insert(openlist,listk,{x=curSquare.x+1, y=curSquare.y, g=tempG,
h=tempH, f=tempG+tempH, par=closedk})
end
-- Add point to the left of current base point
if leftOK then
listk=listk+1
tempH=math.abs((curSquare.x-1)-targetX)+math.abs(curSquare.y-targetY)
table.insert(openlist,listk,{x=curSquare.x-1, y=curSquare.y, g=tempG,
h=tempH, f=tempG+tempH, par=closedk})
end
-- Add point on the top of current base point
if downOK then
listk=listk+1
tempH=math.abs(curSquare.x-targetX)+math.abs((curSquare.y+1)-targetY)
table.insert(openlist,listk,{x=curSquare.x, y=curSquare.y+1, g=tempG,
h=tempH, f=tempG+tempH, par=closedk})
end
-- Add point on the bottom of current base point
if upOK then
listk=listk+1
tempH=math.abs(curSquare.x-targetX)+math.abs((curSquare.y-1)-targetY)
table.insert(openlist,listk,{x=curSquare.x, y=curSquare.y-1, g=tempG,
h=tempH, f=tempG+tempH, par=closedk})
end
table.remove(openlist,curSquareIndex)
listk=listk-1
if closedlist[closedk].x==targetX and closedlist[closedk].y==targetY then
return closedlist
end
end
return nil
end
function CalcPath(closedlist)
if closedlist == nil then
return nil
end
local path = {}
local pathIndex = {}
local last = table.getn(closedlist)
table.insert(pathIndex, 1, last)
local i = 1
while pathIndex[i] > 1 do
i = i + 1
table.insert(pathIndex, i, closedlist[pathIndex[i - 1]].par)
end
for n = table.getn(pathIndex), 1, -1 do
table.insert(path,{x=closedlist[pathIndex[n]].x, y=closedlist[pathIndex[n]].y})
end
closedlist = nil
return path
end
--# ListScroll
-- List Scroll class
--
-- Developed by @sanit February 6, 2012
--
-- Version 1.0
ListScroll = class()
function ListScroll:init(pos,w,h)
-- you can accept and set parameters here
self.pos = pos
self.width= w
self.height= h
self.itemHeight = 0
self.borderColor = color(100,100,100,255)
self.borderWidth = 2
self.backColor = color(255,255,255,255)
self.sepColor = color(100,100,100,255)
self.rowColor1 = color (255,255,255,255)
self.rowColor2 = color(186, 186, 186, 100)
self.allowEdit = false
self.allowDelete = true
self.allowInsert = false
self.multiSelected = false
self.items = {}
self.posY = self.pos.y+self.height
self.prevState = nil
self.selectedItem = 1
end
function ListScroll:add(i)
self.itemHeight = i.height
i.width = self.width
table.insert(self.items,i)
end
function ListScroll:selectedItem()
return self.selectedItem
end
function ListScroll:selectedValue()
if self.selectedItem==nil then
return nil
end
return self.items[self.selectedItem].value
end
function ListScroll:itemCount()
return #self.items
end
function ListScroll:draw()
-- Codea does not automatically call this method
pushStyle()
strokeWidth(self.borderWidth)
fill(self.backColor)
stroke(self.borderColor)
rect(self.pos.x,self.pos.y,self.width,self.height)
clip(self.pos.x,self.pos.y,self.width,self.height)
for i=1,#self.items do
strokeWidth(1)
if i%2==0 then
fill(self.rowColor1)
else
fill(self.rowColor2)
end
self.items[i]:draw(self.pos.x,self.posY-i*self.itemHeight)
end
noClip()
popStyle()
end
function ListScroll:touched(touch)
-- Codea does not automatically call this method
if not (touch.x>=self.pos.x and touch.x<=self.pos.x+self.width and
touch.y>=self.pos.y and touch.y<=self.pos.y+self.height) then
return
end
if touch.state==MOVING then
self.prevState=MOVING
self.posY = self.posY + touch.deltaY
if self.posY>=self.pos.y+self.height+self.itemHeight*(#self.items-1) then
self.posY=self.pos.y+self.height+self.itemHeight*(#self.items-1)
end
elseif touch.state==ENDED then
if self.prevState==MOVING then
if self.posY<self.pos.y+self.height then
self.posY=self.pos.y+self.height
end
self.prevState=nil
else
local i = math.ceil((self.posY-touch.y)/self.itemHeight)
if i<=#self.items then
self.selectedItem = i
self.items[i]:touched(touch)
if not self.multiSelected then
for r=1,#self.items do
if r ~= i then
self.items[r].selected=false
end
end
end
else
self.selectedItem = nil
end
end
end
end
--# Main
-- dGenerator
--
-- This program is a WYSIWYG Level Editor which can be used
-- in a tower defence or Rogue type game.
--
-- We will use it to demonstrate our A* pathfinding tutorial.
supportedOrientations(ANY)
DEBUG = true -- If true additional debug information is printed.
-- Define the grid cell and App states
stateNil = 0
stateStart = 1
stateEnd = 2
stateClear = 3
stateObstacle = 4
stateCheck = 5
stateToggleGrid = 6
stateToggleDebug = 7
stateShowFileManager = 8
currentState = stateNil
function setup()
version = 1.1
saveProjectInfo("Description", "Level Generator for Tower Defence Game v"..version)
saveProjectInfo("Author", "Reefwing Software")
saveProjectInfo("Date", "30 August 2012")
saveProjectInfo("Version", version)
saveProjectInfo("Comments", "Original Release.")
print("-- dGenerator v"..version.."\n")
-- We will keep track of whether we have selected the
-- start and finish cell and if so the co-ordinates of
-- these two cells. This will save us having to iterate
-- through the entire table to find them.
startCellSelected = false
endCellSelected = false
startPos = nil
endPos = nil
-- Create a global variable to hold the current tap state
tapState = stateStart
-- Initialise the grid matrix, we will reuse a lot of the
-- code from our MineSweeper App. We have selected a sprite
-- size of 32 so you can use Spritely to generate your sprites
-- if you want. We used Sprite Something this time.
mSpriteSize = 32
gridWidth = 15
gridHeight = 15
-- baseX = WIDTH/2 - (mSpriteSize * gridWidth) / 2 + 15
baseX = 30
grid = {}
createGrid()
-- Create the 4 tap state menu select buttons & 4 action buttons
local bX = baseX + 33
local bY = 50
startButton = Button(bX, bY, true, "Start")
startButton.tag = stateStart
startButton.action = function()buttonPressed(stateStart) end
endButton = Button(bX + 74, bY, false, "End")
endButton.tag = stateEnd
endButton.action = function()buttonPressed(stateEnd) end
clearButton = Button(bX + 148, bY, false, "Path")
clearButton.tag = stateClear
clearButton.action = function()buttonPressed(stateClear) end
wallButton = Button(bX + 222, bY, false, "Wall")
wallButton.tag = stateWall
wallButton.action = function()buttonPressed(stateObstacle) end
gridButton = Button(bX + 326, bY, false, "Grid")
gridButton.tag = stateToggleGrid
gridButton.action = function()buttonPressed(stateToggleGrid) end
checkButton = Button(bX + 400, bY, false, "A*")
checkButton.tag = stateCheck
checkButton.action = function()buttonPressed(stateCheck) end
debugButton = Button(bX + 474, bY, true, "dBug")
debugButton.tag = stateToggleDebug
debugButton.action = function()buttonPressed(stateToggleDebug) end
exitButton = Button(bX + 548, bY, false, "Exit")
exitButton.action = function()close() end
-- Unlike the other buttons, we want the checkButton to be
-- momentary. To create this illusion we will use a counter
-- called checkCounter which will turn off the button after
-- 0.5 seconds.
checkCounter = 0
-- It is actually easier to use the procedurally generated
-- mesh buttons, so we will use these for the Load, Save and
-- Reset buttons.
local x, y = baseX - mSpriteSize/2, HEIGHT - 128
resetButton = MeshButton("Reset", x, y, 128, 50)
resetButton.action = function() resetGrid() end
loadButton = MeshButton("Load", x + 178, y, 128, 50)
loadButton.action = function() loadLevel() end
saveButton = MeshButton("Save", x + 356, y, 128, 50)
saveButton.action = function() saveLevel() end
-- Create the game name and level text boxes
y = HEIGHT - 50
gameNameTextBox = TextBox(x, y, 178+128, "<Enter Game Name>")
levelTextBox = TextBox(x + 356, y, 128, "<Level>")
-- Initialise the FileManager class
FileManager:init()
end
-- Menu Bar Call Back Methods
function cancelFileManager()
-- Custom implementation goes here
currentState = stateNil
end
function aboutFileManager()
-- Custom implementation goes here
end
function saveFile()
-- Custom implementation goes here
end
function loadFile()
-- Once file is selected load level
if currentDirectory == GlobalData and globalKeyTable[currentKey] ~= nil then
local keyString = globalKeyTable[currentKey]
local valString = readGlobalData(globalKeyTable[currentKey])
if DEBUG then
print("-- Loading Key: " .. keyString)
end
if string.starts(keyString, "dGEN") then
print("dGen - valid data key found.")
local keyArray = explode(",", keyString)
gameNameTextBox.text = keyArray[2]
levelTextBox.text = keyArray[3]
if string.match(keyArray[4], "true") == "true" then
print("dGen - start cell selected.")
startCellSelected = true
if startPos == nil then
startPos = vec2(keyArray[5], keyArray[6])
else
startPos.x = keyArray[5]
startPos.y = keyArray[6]
end
else
print("dGen - start cell not selected.")
startCellSelected = false
startPos.x = nil
startPos.y = nil
end
if string.match(keyArray[7], "true") == "true" then
print("dGen - end cell selected.")
endCellSelected = true
if endPos == nil then
endPos = vec2(keyArray[8], keyArray[9])
else
startPos.x = keyArray[8]
startPos.y = keyArray[9]
end
else
print("dGen - end cell not selected.")
endCellSelected = false
endPos.x = nil
endPos.y = nil
end
generateGrid = loadstring(valString)
createGrid(generateGrid())
print("dGen - Grid loaded.")
else
print("dGen ERROR - invalid file type.")
end
end
currentState = stateNil
end
function deleteFile()
if currentDirectory == nil then
-- No Directory Selected
elseif currentDirectory == ProjectData and projectKeyTable[currentKey] ~= nil then
saveProjectData(projectKeyTable[currentKey], nil)
loadProjectKeys()
displayKeys(projectKeyTable)
currentKey = 1
valueString = string.truncate(readProjectData(projectKeyTable[currentKey]) or "nil",150)
elseif currentDirectory == ProjectInfo and infoKeyTable[currentKey] ~= nil then
-- delete is not currently supported for ProjectInfo because we can't
-- get a list of the keys in this pList.
elseif currentDirectory == LocalData and localKeyTable[currentKey] ~= nil then
saveLocalData(localKeyTable[currentKey], nil)
loadLocalKeys()
displayKeys(localKeyTable)
currentKey = 1
valueString = string.truncate(readLocalData(localKeyTable[currentKey]) or "nil",150)
elseif currentDirectory == GlobalData and globalKeyTable[currentKey] ~= nil then
saveGlobalData(globalKeyTable[currentKey], nil)
loadGlobalKeys()
displayKeys(globalKeyTable)
currentKey = 1
valueString = string.truncate(readGlobalData(globalKeyTable[currentKey]) or "nil",150)
end
end
-- dGenerator Draw() functions
function draw()
-- This sets a dark background color
background(codeaDarkBackground)
if currentState == stateFileManager then
FileManager:draw()
else
-- Draw the grid
drawGrid()
-- Draw the tap state button tool bar
pushStyle()
local toolBarX = baseX - mSpriteSize/2
fill(lightGrayColor)
stroke(whiteColor)
strokeWidth(4)
rect(toolBarX, 7, 320, 84)
lineCapMode(SQUARE)
stroke(blackColor)
strokeWidth(6)
line(toolBarX + 2, 7, toolBarX + 2, 91)
line(toolBarX, 90, toolBarX + 320, 90)
-- If the checkButton is pressed start the checkCounter timer
-- this will unpress the button after 0.5 secs.
if checkButton.pressed then
checkCounter = checkCounter + DeltaTime
if checkCounter > 1 then
checkButton.pressed = false
checkCounter = 0
end
end
-- Draw the buttons
startButton:draw()
endButton:draw()
clearButton:draw()
wallButton:draw()
gridButton:draw()
checkButton:draw()
debugButton:draw()
exitButton:draw()
resetButton:draw()
loadButton:draw()
saveButton:draw()
-- And the Text Boxes
gameNameTextBox:draw()
levelTextBox:draw()
popStyle()
end
end
function drawGrid()
-- Iterate through the grid matrix and draw each cell
for i = 1, gridWidth do
for j = 1, gridHeight do
grid[i][j]: draw()
end
end
end
-- Load and Save Level Functions
function explode(div,str)
if (div=='') then return false end
local pos,arr = 0,{}
-- for each divider found
for st,sp in function() return string.find(str,div,pos,true) end do
table.insert(arr,string.sub(str,pos,st-1)) -- Attach chars left of current divider
pos = sp + 1 -- Jump past current divider
end
table.insert(arr,string.sub(str,pos)) -- Attach chars right of last divider
return arr
end
function saveLevel()
print("-- Saving Level Data.")
-- Create and populate a table (saveGrid) to save the grid state.
-- This way we can save and retrieve the level data.
--
-- Save Data string format:
--
-- key = "DGEN,gameName,levelNumberstartCellSelected,startPos.x,startPos.y,
-- endCellSelected,endPos.x,endPos.y"
--
-- value = "saveGrid"
local saveGrid = {}
for i = 1, gridWidth do
saveGrid[i] = {} -- create a new row
for j = 1, gridHeight do
saveGrid[i][j] = grid[i][j]:dataToString()
end
end
-- Note that no checks are made regarding whether game name or
-- level number has been entered by the user. Overwriting previous
-- levels also isn't checked.
local key = "dGEN," .. gameNameTextBox.text .. "," .. levelTextBox.text ..","
local suffix
if startCellSelected and startPos ~= nil then
suffix = "true," .. startPos.x .. "," .. startPos.y .. ","
else
suffix = "false,nil,nil,"
end
if endCellSelected and endPos ~= nil then
suffix = suffix .. "true," .. endPos.x .. "," .. endPos.y .. ","
else
suffix = suffix .. "false,nil,nil,"
end
key = key .. suffix
local saveString = DataDumper(saveGrid)
if DEBUG then
print("Key: " .. key)
print("Value: " .. saveString)
end
saveGlobalData(key, saveString)
end
function loadLevel()
print("-- Loading Level Data.")
print("-- Available Keys:")
currentState = stateFileManager
globalDataKeyTable = listGlobalData()
for i, v in ipairs(globalDataKeyTable) do
if string.starts(v, "dGEN") then
print(i..": "..v)
elseif i == #globalDataKeyTable and DEBUG then
print("-- End of Global Data File.")
end
end
end
-- Handle iPad Orientation Changes
function updateGridLocation(newOrientation)
-- This function is required to reposition the grid
-- if the iPad orientation changes.
baseX = 30
local y = HEIGHT/2 - (mSpriteSize * gridHeight) / 2
local x = baseX
for i = 1, gridWidth do
for j = 1, gridHeight do
grid[i][j].pos.x = x
grid[i][j].pos.y = y
x = x + mSpriteSize
end
x = baseX
y = y + mSpriteSize
end
local bX = baseX + 33
startButton.x = bX
endButton.x = bX + 74
clearButton.x = bX + 148
wallButton.x = bX + 222
local rbX, rbY = baseX - mSpriteSize/2, HEIGHT - 128
resetButton.x, resetButton.y = rbX, rbY
loadButton.x, loadButton.y = rbX + 178, rbY
saveButton.x, saveButton.y = rbX + 356, rbY
local tbY = HEIGHT - 50
gameNameTextBox.x, gameNameTextBox.y = rbX, tbY
levelTextBox.x, levelTextBox.y = rbX + 356, tbY
if newOrientation == LANDSCAPE_LEFT or newOrientation == LANDSCAPE_RIGHT then
gridButton.x = bX + 326
checkButton.x = bX + 400
debugButton.x = bX + 474
debugButton.y = checkButton.y
exitButton.x = bX + 548
exitButton.y = gridButton.y
else
local rbY = rbY - 130
gridButton.x = bX + 320
exitButton.x = gridButton.x
exitButton.y = gridButton.y + 74
checkButton.x = bX + 394
debugButton.x = checkButton.x
debugButton.y = checkButton.y + 74
resetButton.y = rbY
loadButton.y = rbY
saveButton.y = rbY
end
end
function orientationChanged(newOrientation)
if currentState == stateShowFileManager then
-- Update ListScroll co-ordinates for new orientation
local y = HEIGHT - 500
if directoryList ~= nil then
directoryList.pos.y = y
end
if dataKeyList ~= nil then
dataKeyList.pos.y = y
end
-- Update Menu Bar co-ordinates for new orientation
y = HEIGHT - 80
if b1tab ~= nil then
for i = 1, #b1tab do
b1tab[i].y = y
end
end
y = HEIGHT - 110
if b2tab ~= nil then
for i = 1, #b2tab do
b2tab[i].y = y
y = y - 30
end
end
else
updateGridLocation(newOrientation)
end
end
-- Grid creation and reset functions
function createGrid(obstacleGrid)
local y = HEIGHT/2 - (mSpriteSize * gridHeight) / 2
local x = baseX
-- Create the grid using nested tables.
-- It operates as a two dimensional array (or matrix)
if DEBUG then
if obstacleGrid == nil then
print("-- dGen: Creating Empty Grid.")
else
print("-- dGen: Creating Grid from File.")
end
end
for i = 1, gridWidth do
grid[i] = {} -- create a new row
for j = 1, gridHeight do
if obstacleGrid == nil then
grid[i][j] = Cell(i, j, stateObstacle, x, y)
else
grid[i][j] = Cell(i, j, obstacleGrid[i][j], x, y)
end
grid[i][j].action = function() handleCellTouch(grid[i][j].index) end
x = x + mSpriteSize
end
x = baseX
y = y + mSpriteSize
end
end
function resetGrid()
-- When the reset button is tapped this function will
-- reset the table
if DEBUG then
print("-- dGen: Resetting Grid.")
end
for i = 1, gridWidth do
for j = 1, gridHeight do
grid[i][j] = nil
end
end
grid = {}
createGrid()
if gridButton.pressed then
toggleGridState()
end
startCellSelected = false
endCellSelected = false
startPos = nil
endPos = nil
end
-- Cell Related Functions
--
-- We discussed closure functions in a separate tutorial, but
-- for now to understand what is going on in the count neighbouring cell
-- functions you need to know that when a function is enclosed in
-- another function, it has full access to local variables from the
-- enclosing function. In this example, inNeighbourCells() increments the local
-- variable obstacleNum in countObstacles().
function inNeighbourCells(startX, endX, startY, endY, closure)
for i = math.max(startX, 1), math.min(endX, gridWidth) do
for j = math.max(startY, 1), math.min(endY, gridHeight) do
closure(i, j)
end
end
end
function countObstacles(index)
local obstacleNum = 0
inNeighbourCells(index.x - 1, index.x + 1, index.y - 1, index.y + 1,
function(x, y) if grid[x][y].state == stateObstacle then
obstacleNum = obstacleNum + 1 end
end)
return obstacleNum
end
-- Button Action Methods
function toggleGridState()
-- This function will toggle whether the grid overlay is shown.
for i = 1, gridWidth do
for j = 1, gridHeight do
grid[i][j].showGrid = not grid[i][j].showGrid
end
end
end
function toggleDebugState()
-- This function will toggle whether the Debug window is shown.
if debugButton.pressed then
displayMode(STANDARD)
DEBUG = true
else
displayMode(FULLSCREEN_NO_BUTTONS)
DEBUG = false
end
end
function clearPath()
-- Clear the path from the Grid.
if DEBUG then
print("-- dGEN: Clearing Path from Grid")
end
for i = 1, gridWidth do
for j = 1, gridHeight do
if grid[i][j].state == statePath then
grid[i][j].state = stateClear
end
end
end
end
function findPath()
-- If a start and end cell has been defined, use
-- the A* algorithm to find and display a path.
if startCellSelected then
if endCellSelected then
print("-- dGEN: Calculating A* Path.")
path = CalcPath(CalcMoves(grid, startPos.x, startPos.y, endPos.x, endPos.y))
if path == nil then
print("-- dGEN No path found.")
else
print("-- dGEN: Path found:\n")
if DEBUG then
print(to_string(path, 2))
end
-- start and end one cell early so we dont overwrite
-- the start and end cell status.
for i = 2, table.getn(path) - 1 do
grid[path[i].x][path[i].y].state = statePath
end
end
else
print("--dGEN ERROR: Path can't be found until end cell is selected.")
end
else
print("--dGEN ERROR: Path can't be found until start cell is selected.")
end
end
-- Touch Handling
function buttonPressed(index)
if index < stateCheck then
tapState = index
end
if index == stateStart then
endButton.pressed = false
clearButton.pressed = false
wallButton.pressed = false
elseif index == stateEnd then
startButton.pressed = false
clearButton.pressed = false
wallButton.pressed = false
elseif index == stateClear then
startButton.pressed = false
endButton.pressed = false
wallButton.pressed = false
elseif index == stateObstacle then
startButton.pressed = false
endButton.pressed = false
clearButton.pressed = false
elseif index == stateToggleGrid then
toggleGridState()
elseif index == stateCheck then
findPath()
elseif index == stateToggleDebug then
toggleDebugState()
else
print("-- dGEN WARNING: Unknown button index pressed.")
end
end
function touched(touch)
if currentState == stateFileManager then
FileManager:touched(touch)
else
-- Pass through touch handling to buttons, textbox and the grid cells
startButton:touched(touch)
endButton:touched(touch)
clearButton:touched(touch)
wallButton:touched(touch)
gridButton:touched(touch)
checkButton:touched(touch)
debugButton:touched(touch)
exitButton:touched(touch)
resetButton:touched(touch)
saveButton:touched(touch)
loadButton:touched(touch)
gameNameTextBox:touched(touch)
levelTextBox:touched(touch)
for i = 1, gridWidth do
for j = 1, gridHeight do
grid[i][j]:touched(touch)
end
end
end
end
function handleCellTouch(index)
if tapState == stateStart and startCellSelected then
print("-- dGEN Warning: Only one cell may be assigned as the start cell.")
elseif tapState == stateEnd and endCellSelected then
print("-- dGEN Warning: Only one cell may be assigned as the end cell.")
else
if tapState == stateStart then
startCellSelected = true
startPos = vec2(index.x, index.y)
elseif tapState == stateEnd then
endCellSelected = true
endPos = vec2(index.x, index.y)
end
if grid[index.x][index.y].state == stateStart then
startCellSelected = false
startPos = nil
clearPath()
elseif grid[index.x][index.y].state == stateEnd then
endCellSelected = false
endPos = nil
clearPath()
end
grid[index.x][index.y].state = tapState
end
end
-- KeyBoard handling function
-- Used to enter game name and level
function keyboard(key)
-- Add text to the textbox which has focus
local mTextBox = gameNameTextBox
if levelTextBox.hasFocus then
mTextBox = levelTextBox
end
if key ~= nil then
if string.byte(key) == 10 then -- <RETURN> Key pressed
hideKeyboard()
mTextBox.hasFocus = false
elseif string.byte(key) ~= 44 then -- filter out commas
mTextBox:acceptKey(key)
end
end
end
-- Some String Helper Functions:
function string.starts(String, Start)
return string.sub(String, 1, string.len(Start)) == Start
end
--# MeshButton
MeshButton = class()
-- Fast Mesh Button Class courtesy of @Vega
-- 3 Aug 2012
--
-- Modified: - Call Back Functionality added
-- - pushStyle() & popStyle() added to draw()
-- - tapped status added
-- - pointInRect() function added
-- - changed vec2 location to x and y (to reduce typing!)
--
-- Version 1.3 (4 Aug 2012)
function MeshButton:init(text,x,y,width,height)
self.state = "normal"
self.text = text
self.textColor = color(255,255,255,192)
self.x = x
self.y = y
self.width = width
self.height = height
self.visible = true
self.fontSize = 28
self.font = "ArialRoundedMTBold"
self.color1 = color(255, 255, 255, 96)
self.color2 = color(128,128,128,32)
self.presscolor1 = color(192, 224, 224, 128)
self.presscolor2 = color(96, 192, 224, 128)
self.verts = self:createVerts(self.width, self.height)
self.myMesh = mesh()
self.myMesh.vertices = triangulate(self.verts)
self.vertColor = {}
self:recolor()
self.action = nil
self.tapped = false
end
function MeshButton:setColors(c1,c2,p1,p2)
self.color1 = c1
self.color2 = c2
self.presscolor1 = p1
self.presscolor2 = p2
self:recolor()
end
function MeshButton:textOptions(fn, sz, col)
self.font = fn
self.fontSize = sz
self.textColor = col
end
function MeshButton:draw()
if self.visible == true then
pushStyle()
pushMatrix()
translate(self.x,self.y)
self.myMesh:draw()
fill(self.textColor)
fontSize(self.fontSize)
font(self.font)
text(self.text, self.width/2,self.height/2)
self:drawLines(self.verts)
popMatrix()
popStyle()
end
end
function MeshButton:touched(touch)
self.tapped = false
if self.visible then
if pointInRect(touch.x, touch.y, self.x, self.y, self.width, self.height) then
if touch.state == BEGAN then
self.tapped = true
self.state = "pressing"
self:recolor()
elseif touch.state == ENDED then
if self.state == "pressing" then
self.state = "normal"
self.tapped = true
self:recolor()
end
if self.action then
self.action()
end
end
else
self.state = "normal"
self:recolor()
end
end
end
function MeshButton:createVerts(w,h)
local r
local v = {}
if w > 100 or h > 100 then
if w>=h then r = math.round(h/100) else r = math.round(w/100) end
else
r = 1
end
v[1] = vec2(w,6*r)
v[2] = vec2(w-r,4*r)
v[3] = vec2(w-2*r,2*r)
v[4] = vec2(w-4*r,r)
v[5] = vec2(w-6*r,0)
v[6] = vec2(6*r,0)
v[7] = vec2(4*r,r)
v[8] = vec2(2*r,2*r)
v[9] = vec2(r,4*r)
v[10] = vec2(0,6*r)
v[11] = vec2(0,h-6*r)
v[12] = vec2(r,h-4*r)
v[13] = vec2(2*r,h-2*r)
v[14] = vec2(4*r,h-r)
v[15] = vec2(6*r,h)
v[16] = vec2(w-6*r,h)
v[17] = vec2(w-4*r,h-r)
v[18] = vec2(w-2*r,h-2*r)
v[19] = vec2(w-r,h-4*r)
v[20] = vec2(w,h-6*r)
return v
end
function MeshButton:drawLines(v)
noSmooth()
strokeWidth(1)
stroke(0, 0, 0, 192)
for i=1, #v-1 do
line(v[i].x,v[i].y,v[i+1].x,v[i+1].y)
end
line(v[#v].x,v[#v].y,v[1].x,v[1].y)
end
function MeshButton:recolor()
local lt, dk
if self.state == "normal" then
lt = self.color1
dk = self.color2
else
lt = self.presscolor1
dk = self.presscolor2
end
for i=1,3 * #self.verts - 6 do
if self.myMesh.vertices[i].y > self.height/2 then
self.vertColor[i] = lt
else
self.vertColor[i] = dk
end
end
self.myMesh.colors = self.vertColor
end
function pointInRect(pointX, pointY, x, y, w, h)
-- Returns true if point (pointX, pointY) is within the rectangle
-- with lower left corner at (x, y) with a width of w and a
-- height of h.
--
-- Reefwing Software (www.reefwing.com.au)
-- Version 1.0
if pointX >= x and pointX <= x + w and pointY >= y and pointY <= y + h then
return true
else
return false
end
end
function math.round(value)
-- math.round function courtesy of Vega.
return math.floor(value + 0.5)
end
--# TextItem
-- Text Item class
--
-- Developed by @sanit February 6, 2012
--
-- Version 1.0
TextItem = class()
function TextItem:init(id,value)
-- you can accept and set parameters here
self.pos = vec2(0,0)
self.id = id
self.value=value
self.selected=false
self.selectedColor = color(223, 218, 150, 255)
self.width = 0
self.height = 0
self.textColor = color(0,0,0,255)
self.textAlign=LEFT
self.fontName = "Courier"
self.fontSize=24
font(self.fontName)
fontSize(self.fontSize)
_,self.height = textSize(self.fontName)
end
function TextItem:draw(x,y)
-- Codea does not automatically call this method
self.pos = vec2(x,y)
pushStyle()
strokeWidth(1)
stroke(stroke())
if self.selected then
fill(self.selectedColor)
end
rect(self.pos.x,self.pos.y,self.width,self.height)
font(self.fontName)
fontSize(self.fontSize)
textMode(CORNER)
textAlign(self.textAlign)
textWrapWidth(self.width)
fill(self.textColor)
text(self.value,self.pos.x+5,self.pos.y)
popStyle()
end
function TextItem:touched(touch)
-- Codea does not automatically call this method
if self.selected then
self.selected=false
else
self.selected=true
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment