Skip to content

Instantly share code, notes, and snippets.

@tarrouye
Last active April 16, 2019 07:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tarrouye/44c541318700bbe3c687 to your computer and use it in GitHub Desktop.
Save tarrouye/44c541318700bbe3c687 to your computer and use it in GitHub Desktop.
GAMEJAM
--# Main
-- Puzzle
-- Game Jam entry (https://codea.io/talk/discussion/6361/game-jam-2015-winners-announced/p1)
-- Copyright 2015 Théo Arrouye
displayMode(FULLSCREEN)
supportedOrientations(PORTRAIT)
function setup()
font("Futura-CondensedMedium") textAlign(CENTER)
music("Dropbox:Little Town", true)
if not readProjectData("levelPacks") then
levelPacks = {}
levelPacks[1] = {
name = "4 x 5", levels = { {
{"s", "s", " ", " "},
{"s", " ", "s", " "},
{"s", " ", " ", " "},
{" ", "s", " ", "s"},
{" ", " ", " ", "f"}
} }
}
saveProjectTable("levelPacks", levelPacks)
end
globalTouches = {}
loadstring(readProjectData("levelPacks", "levelPacks = {}"))()
loadstring(readLocalData("completedLevels", "completedLevels = {}"))()
screen = Selection()
end
function draw()
background(255)
clip(0, 0, WIDTH, HEIGHT)
screen:draw()
end
function touched(t)
screen:touched(t)
end
--# Selection
Selection = class()
function Selection:init()
self.game = Game()
self.editor = LevelEditor()
self.state = "pack"
self.drawState = {}
self.drawState["pack"] = self.drawPack
self.drawState["level"] = self.drawLevel
self.drawState["game"] = function() self.game:draw() end
self.drawState["editor"] = self.drawEditor
self.transition = { fade = 0, x = WIDTH / 2, y = HEIGHT / 2, size = math.sqrt(WIDTH^2 + HEIGHT^2) * 2,
colour = color(255), fontsize = 0, message = "" }
self.bubbleSize = WIDTH / 5
self:makeButtons()
end
function Selection:makeButtons()
self.buttons = {}
for i, pack in ipairs(levelPacks) do
local x, y = (-WIDTH / 6) + ((WIDTH / 3) * i), HEIGHT / 2
local btn = CircleButton(pack.name, x, y, self.bubbleSize, color(0, 194, 255, 255), color(0))
btn.callback = function() if not btn.expanded then self:selectPack(i) else self:deselectPack() end end
self.buttons[i] = { main = btn, name = pack.name, sub = {} }
local beatAllLevs = true
for ii, level in ipairs(pack.levels) do
local x = self.bubbleSize + (self.bubbleSize * 1.5)*(specialModulo(ii, 3)-1)
local y = HEIGHT / 2 + self.bubbleSize * 1.5 - (self.bubbleSize * 1.5 * math.floor((ii-1) / 3))
local btn = CircleButton(ii, x, y, self.bubbleSize, color(255, 255, 255, 255), color(0))
btn.callback = function() self:selectLevel(ii) end
local completed = (completedLevels["Pack: " .. i ..", Level: " .. ii] == true)
if not completed then
beatAllLevs = false
end
self.buttons[i].sub[ii] = { btn = btn, completed = completed }
end
self.buttons[i].completed = beatAllLevs
local y = HEIGHT / 2 - self.bubbleSize * 1.5 * 2
local xbutton = CircleButton("x", WIDTH / 2, y, self.bubbleSize / 2, color(255), color(0))
xbutton.callback = function() self:deselectPack() end
self.buttons[i].sub[#self.buttons[i].sub + 1] = { btn = xbutton }
end
local ebtn = CircleButton("Editor", WIDTH / 2, self.bubbleSize, self.bubbleSize, color(0, 194, 255, 255), color(0))
ebtn.callback = function() self:loadEditor() end
table.insert(self.buttons, { main = ebtn })
end
function Selection:updateCompleted()
for i, pack in ipairs(levelPacks) do
local allCompleted = true
for ii, level in ipairs(pack.levels) do
local completed = (completedLevels["Pack: " .. i ..", Level: " .. ii] == true)
if not completed then
allCompleted = false
end
self.buttons[i].sub[ii].completed = completed
end
if self.buttons[i].sub ~= nil then
self.buttons[i].completed = allCompleted
else
self.buttons[i].completed = nil
end
end
end
function Selection:runTransition(target, callback, initial, time)
callback = callback or function() end
initial = initial or {}
-- Set intial values
for id, value in pairs(initial) do
self.transition[id] = value
end
-- Set tween in action
self.transition.running = tween(time or 0.5, self.transition, target, tween.easing.linear, function()
self.transition.running = nil
callback()
end)
end
function Selection:reset()
self.selectedPack = nil
for i, tbl in ipairs(self.buttons) do
if tbl.main.expanded then
tbl.main:unexpand()
end
if tbl.sub then
for ii, btn in ipairs(tbl.sub) do
if btn.expanded then
btn:unexpand()
end
end
end
end
end
function Selection:deselectPack()
tween(0.3, self.selectedPack, { fade = 0 }, tween.easing.linear)
self.state = "pack"
local exb = self.selectedPack.main
self:runTransition({ x = exb.x, y = exb.y, size = exb.size }, function() self.selectedPack = nil end,
{ x = WIDTH / 2, y = HEIGHT * 19/20, fontsize = exb.fontsize, message = exb.txt,
size = math.sqrt(WIDTH^2 + HEIGHT^2) * 2 })
end
function Selection:selectPack(id)
self.game:loadPack(id)
local exb = self.buttons[id].main
self:runTransition({ x = WIDTH / 2, y = HEIGHT * 19/20, size = math.sqrt(WIDTH^2 + HEIGHT^2) * 2 }, function()
self.selectedPack = self.buttons[id]
self.selectedPack.fade = 0
tween(0.5, self.selectedPack, { fade = 1 })
self.state = "level"
end, { fade = 1, x = exb.x, y = exb.y, fontsize = exb.fontsize, size = exb.size, colour = exb.bColour, message = exb.txt })
end
function Selection:selectLevel(level)
self.game.currentLevel = level
self.game:loadLevel()
local exb = self.selectedPack.sub[level].btn
self:runTransition({ x = WIDTH / 2, y = HEIGHT / 2, size = math.sqrt(WIDTH^2 + HEIGHT^2) * 2 }, function()
self.state = "game"
self:runTransition({ fade = 0 }, function()
self:reset()
end)
end, { fade = 1, x = exb.x, y = exb.y, size = exb.size, colour = exb.bColour, fontsize = 0 })
end
function Selection:loadEditor()
self.editor:reset()
local exb = self.buttons[#self.buttons].main
self:runTransition({ x = WIDTH / 2, y = HEIGHT / 2, size = math.sqrt(WIDTH^2 + HEIGHT^2) * 2 }, function()
self.state = "editor"
self:runTransition({ fade = 0 }, function()
self:reset()
end)
end, { fade = 1, x = exb.x, y = exb.y, size = exb.size, colour = exb.bColour, fontsize = 0 })
end
function Selection:drawPack()
fill(0) fontSize(WIDTH / 20)
text("Menu", WIDTH / 2, HEIGHT * 19/20)
for i, pack in ipairs(self.buttons) do
if self.selectedPack ~= pack then
if pack.completed ~= nil then
if pack.completed then
fill(0, 255, 24, 255) noStroke()
else
fill(255, 0, 0)
end
ellipse(pack.main.x, pack.main.y, pack.main.size * 1.05)
end
pack.main:draw()
end
end
fill(0) fontSize(WIDTH / 40)
text("A JakAttak Game", WIDTH / 2, WIDTH / 35)
end
function Selection:drawLevel()
fill(self.selectedPack.main.bColour) rect(-1, -1, WIDTH + 2, HEIGHT + 2)
fill(0) fontSize(self.selectedPack.main.fontsize)
text(self.selectedPack.name, WIDTH / 2, HEIGHT * 19/20)
for i, subt in ipairs(self.selectedPack.sub) do
local alpha = self.selectedPack.fade * 255
if subt.completed ~= nil then
if subt.completed then
fill(255 - alpha, 255, 255 - alpha, alpha)
else
fill(255, 255 - alpha, 255 - alpha, alpha)
end noStroke()
ellipse(subt.btn.x, subt.btn.y, subt.btn.size * 1.05)
end
subt.btn.alpha = alpha
subt.btn:draw()
end
end
function Selection:drawEditor()
self.editor:draw()
if self.editor.finished then
self:runTransition({ size = math.sqrt(WIDTH^2 + HEIGHT^2) * 2 }, function()
self.state = "pack"
self:runTransition({ fade = 0 })
end, { y = 0, size = 0, fade = 1, fontsize = 0, colour = color(255) } )
self.editor.finished = false
end
end
function Selection:draw()
pushStyle()
self.drawState[self.state](self)
if self.transition.running then
noStroke()
fill(self.transition.colour.r, self.transition.colour.g, self.transition.colour.b, (self.transition.fade * 255))
ellipse(self.transition.x, self.transition.y, self.transition.size)
if self.transition.fontsize > 0 then
fill(0, self.transition.fade * 255) fontSize(self.transition.fontsize)
text(self.transition.message, self.transition.x, self.transition.y)
end
end
popStyle()
end
function Selection:touched(t)
if self.transition.running then return end
if self.state == "pack" then
for i, btn in ipairs(self.buttons) do
btn.main:touched(t)
end
elseif self.state == "level" then
for i, subt in ipairs(self.selectedPack.sub) do
subt.btn:touched(t)
end
elseif self.state == "game" then
self.game:touched(t)
if self.game.finishedPack and not self.game.transition.running then
self:updateCompleted()
self:runTransition({ size = math.sqrt(WIDTH^2 + HEIGHT^2) * 2 }, function()
self.state = "pack"
self:runTransition({ fade = 0 })
end, { y = 0, size = 0, fade = 1, fontsize = 0, colour = color(255) } )
end
elseif self.state == "editor" then
self.editor:touched(t)
end
end
--# Game
Game = class()
function Game:init()
self.levels = (levelPacks[1] or { levels = {{{}}} }).levels
self.transition = { cfade = 1, fade = 0, x = WIDTH / 2, y = 0, size = 0 }
self.paused = false
self.time = 0
self.currentPack = 1
self.currentLevel = 1
self.board = Board()
self.board:setSize(vec2(WIDTH, HEIGHT * 6/7))
self.board:loadLevel(self.levels[self.currentLevel])
self.buttons = {}
self.buttons.pause = CircleButton("||", WIDTH * 3/4, HEIGHT / 7, WIDTH / 8, color(255, 0, 0), color(0),
function() self:togglePause() end)
self.buttons.restart = CircleButton("r", WIDTH / 2, HEIGHT / 7, WIDTH / 8, color(255, 0, 0), color(0),
function() self:restart() end)
self.buttons.exit = CircleButton("x", WIDTH / 4, HEIGHT / 7, WIDTH / 8, color(255, 0, 0), color(0),
function() self.finishedPack = true end)
end
function Game:runTransition(target, callback, initial, time)
callback = callback or function() end
initial = initial or {}
-- Set intial values
for id, value in pairs(initial) do
self.transition[id] = value
end
-- Set tween in action
self.transition.running = tween(time or 0.5, self.transition, target, tween.easing.linear, function()
self.transition.running = nil
callback()
end)
end
function Game:restart()
self:runTransition({ size = math.sqrt(WIDTH^2 + HEIGHT^2) * 2, x = WIDTH / 2, y = HEIGHT / 2 }, function()
self:loadLevel(true)
self.paused = true
self:runTransition({ cfade = 0 }, function()
self.transition.cfade = 1
self.transition.size = 0
self.transition.y = 0
self.paused = false
end)
end, { x = WIDTH / 2, y = 0, size = 0, fade = 0, cfade = 1, colour = color(255) })
end
function Game:togglePause()
if not self.paused then
self.buttons.pause:expand()
self.paused = true
else
self.buttons.pause:unexpand(function() self.paused = false end)
end
end
function Game:loadPack(pack)
self.finishedPack = false
self.currentPack = pack
self.levels = levelPacks[pack].levels
end
function Game:loadLevel(donttransition)
self.board:loadLevel(self.levels[self.currentLevel])
self.paused = true
self.time = 0
local cb = function() self.paused = false self.setinmotion = false end
if not donttransition then
self:runTransition({ size = 0, fade = 0, y = 0 }, cb)
else
cb()
end
end
function Game:saveLevelCompleted()
completedLevels["Pack: " .. self.currentPack ..", Level: " .. self.currentLevel] = true
saveLocalTable("completedLevels", completedLevels)
if self.currentLevel == #self.levels then
self.finishedPack = true
end
end
function Game:draw()
pushStyle()
self.board:draw()
if not self.paused then
self.time = self.time + DeltaTime
end
fill(248, 255, 0, 255) noStroke()
rect(0, 0, WIDTH, HEIGHT / 7)
fill(255, 0, 0) fontSize(WIDTH / 15)
text(formatTime(self.time), WIDTH / 4, HEIGHT / 14)
for i, btn in pairs(self.buttons) do
btn:draw()
end
if self.board.finished and not self.setinmotion then
local bcol, btxt
if self.board.finished == "success" then
self:saveLevelCompleted()
bcol = color(17, 255, 0, 255)
btxt = "Success!"
if self.currentLevel == #self.levels then
btxt = btxt .. "\n\nCompleted last level of pack"
end
elseif self.board.finished == "failed" then
bcol = color(255, 0, 0, 255)
btxt = "Failed."
end
self:runTransition({ size = math.sqrt(WIDTH^2 + HEIGHT^2) * 2, y = HEIGHT / 2, fade = 1 }, nil,
{ colour = bcol, message = btxt, fade = -0.25 })
self.setinmotion = true
end
if self.transition.size > 0 then
fill(self.transition.colour.r, self.transition.colour.g, self.transition.colour.b, self.transition.cfade * 255)
ellipse(self.transition.x, self.transition.y, self.transition.size)
end
if self.transition.fade > 0 then
fill(0, 255 * self.transition.fade) fontSize(WIDTH / 20)
text(self.transition.message, self.transition.x, self.transition.y)
end
popStyle()
end
function Game:touched(t)
if self.transition.size == 0 then
if not self.paused then
self.board:touched(t)
end
for i, btn in pairs(self.buttons) do
btn:touched(t)
end
end
if t.state == ENDED and self.board.finished and not self.transition.running then
if self.board.finished == "success" then
self.currentLevel = math.min(self.currentLevel + 1, #self.levels)
end
self:loadLevel()
end
end
--# Board
Board = class()
local defaultMap = { {} }
local defaultSize = vec2(WIDTH, HEIGHT * 5/6)
local tileMoveDuration = 0.35
function Board:init()
self:initVariables()
self:resetDefaults()
end
function Board:initVariables()
self.pieceInstances = {}
for id, piece in pairs(pieces) do
self.pieceInstances[id] = piece()
end
end
function Board:resetDefaults()
self.map = defaultMap
self.size = defaultSize
self:calculateTileSize()
self:calculateTiles()
end
function Board:setMap(tiles)
self.map = tiles
self:calculateTileSize()
self:calculateTiles()
end
function Board:setSize(size)
self.size = size
self:calculateTileSize()
end
function Board:calculateTiles()
self.tiles = {}
for r = #self.map, 1, -1 do
for c = 1, #self.map[r], 1 do
if self.pieceInstances[self.map[r][c]] then
local x = WIDTH - ((#self.map[r] - c + 1) * self.tileSize.x) + self.tileSize.x / 2
local y = HEIGHT - (r * self.tileSize.y) + self.tileSize.y / 2
local mstile = { x = x, y = y, fade = 0, key = self.map[r][c] }
if r == 1 and c == 1 then
self.player = { x = x, y = y, tile = self.map[r][c] }
mstile.currentlyPlayer = true
end
table.insert(self.tiles, mstile)
end
end
end
end
function Board:calculateTileSize()
self.tileSize = vec2( self.size.x / #self.map[1], self.size.y / #self.map )
end
function Board:highlightNextMoves()
local possibleMoves = 0
for i, tile in ipairs(self.tiles) do
tile.allowedMove, tile.shiftAnyways = false, false
local coord = vec2(math.signOrZero(tile.x - self.player.x), math.signOrZero(tile.y - self.player.y))
if not tile.currentlyPlayer
and ((math.abs(self.player.y - tile.y) - self.tileSize.y <= 0.01 and math.abs(self.player.x - tile.x) <= 0.01)
or (math.abs(self.player.x - tile.x) - self.tileSize.x <= 0.01 and math.abs(self.player.y - tile.y) <= 0.01)) then
if self.pieceInstances[self.player.tile]:canMoveOut(coord)
and self.pieceInstances[tile.key]:canMoveIn(coord) then
tile.allowedMove = true
possibleMoves = possibleMoves + 1
else
tile.shiftAnyways = true
end
end
end
if possibleMoves == 0 then
self.finished = "failed"
end
end
function Board:loadLevel(tiles)
self.finished = nil
self:setMap(tiles)
self:highlightNextMoves()
-- Add some transition effect code?
end
function Board:drawTile(tile)
self.pieceInstances[tile.key]:draw(tile.x, tile.y, self.tileSize.x, self.tileSize.y)
if tile.fade > 0 then
fill(255, 255 * tile.fade)
rect(tile.x, tile.y, self.tileSize.x, self.tileSize.y)
end
if tile.allowedMove then
fill(255, 250, 0, 120 * self:fade())
rect(tile.x, tile.y, self.tileSize.x, self.tileSize.y)
end
end
function Board:drawPlayer()
fill(228, 0, 255, 255)
ellipse(self.player.x, self.player.y, self.tileSize.x / 3)
end
function Board:draw()
pushStyle()
noStroke() rectMode(CENTER)
for tileId, tile in pairs(self.tiles) do
self:drawTile(tile)
end
self:drawPlayer()
popStyle()
end
function Board:fade()
if self.fadeeffect == nil then
self.fadeeffect = 0
tween(0.5, self, { fadeeffect = 1 }, { loop = tween.loop.pingpong })
end
return self.fadeeffect
end
function Board:tileAtPoint(poignt)
for i, tile in ipairs(self.tiles) do
if poignt.x > tile.x - self.tileSize.x / 2 and poignt.x < tile.x + self.tileSize.x / 2
and poignt.y > tile.y - self.tileSize.y / 2 and poignt.y < tile.y + self.tileSize.y / 2 then
return { id = i, tile = tile}
end
end
end
function Board:movePlayer(tb)
local dir = vec2(tb.tile.x - self.player.x, tb.tile.y - self.player.y)
local toMove = {}
for i, tile in ipairs(self.tiles) do
if (tile.allowedMove or tile.shiftAnyways) and tile ~= tb.tile then
local off = vec2(self.player.x - tile.x, self.player.y - tile.y)
if not self:tileAtPoint(vec2(tb.tile.x - off.x, tb.tile.y - off.y)) then
table.insert(toMove, { tid = i, x = tb.tile.x - off.x, y = tb.tile.y - off.y })
end
elseif tile.currentlyPlayer then
tween(tileMoveDuration, self.tiles[i], { fade = 1 }, tween.easing.linear, function()
table.remove(self.tiles, i)
end)
end
end
for _, move in ipairs(toMove) do
tween(tileMoveDuration, self.tiles[move.tid], { x = move.x, y = move.y })
end
self.player.moving = tween(tileMoveDuration, self.player, { x = tb.tile.x, y = tb.tile.y }, tween.easing.linear,
function()
if tb.tile.key == "f" then
self.finished = "success"
else
self.player.moving = nil
tb.tile.currentlyPlayer = true
self.player.tile = tb.tile.key
self:highlightNextMoves()
end
end)
end
function Board:touched(t)
if self:tileAtPoint(t) and self:tileAtPoint(t).tile.allowedMove and not self.player.moving then
self:movePlayer(self:tileAtPoint(t))
end
end
--# Button
CircleButton = class()
function CircleButton:init(txt, x, y, size, col, col2, cb, fs)
self.txt, self.x, self.y, self.size, self.bColour, self.tColour = txt, x, y, size or 0, col, col2
local cbfs = (self.size / 7) fontSize(cbfs)
local w, h = textSize(self.txt)
while w > (self.size * 0.85) do
cbfs = cbfs * 0.9 fontSize(cbfs)
w, h = textSize(self.txt)
end
self.alpha = 255
self.fontsize = fs or cbfs
self.touching, self.callback = nil, cb or function() end
end
function CircleButton:expand(tbl)
self.preExpanding = { x = self.x, y = self.y, size = self.size, fontsize = self.fontsize }
local tbl = tbl or {}
local target = { x = tbl.x or WIDTH / 2, y = tbl.y or HEIGHT / 2, size = tbl.size or math.sqrt(WIDTH^2 + HEIGHT^2) * 2, fontsize = tbl.fontsize or self.fontsize }
local cb = tbl.callback or function() end
self.expanding = tween(0.35, self, target, tween.easing.linear, function() self.expanded = true self.expanding = nil cb() end)
end
function CircleButton:unexpand(cb)
if self.exanding then
tween.stop(self.expanding)
end
cb = cb or function() end
self.expanding = tween(0.35, self, self.preExpanding, tween.easing.linear, function() self.expanded = false self.expanding = nil cb() end)
end
function CircleButton:draw(alpha, scaleFactor)
if self.disabled then return end
pushMatrix()
translate(self.x, self.y)
if self.touching and not self.expanded then
fill(self.bColour.r / 2, self.bColour.g / 2, self.bColour.b / 2, self.alpha)
else
fill(self.bColour.r, self.bColour.g, self.bColour.b, self.alpha)
end
ellipse(0, 0, self.size)
fill(self.tColour.r, self.tColour.g, self.tColour.b, self.alpha) fontSize(self.fontsize)
text(self.txt)
noTint()
popMatrix()
end
function CircleButton:touched(touch)
if self.disabled then return end
local t = vec2(touch.x, touch.y)
if (t:dist(vec2(self.x, self.y)) <= self.size / 2) then
if touch.state == BEGAN or touch.state == MOVING then
if not globalTouches[touch.id] then
globalTouches[touch.id] = true
self.touching = touch.id
end
elseif self.touching then
self.callback()
if self.touching then
globalTouches[self.touching] = false
end
self.touching = false
end
elseif touch.id == self.touching then
globalTouches[self.touching] = false
self.touching = false
end
end
--# LevelEditor
LevelEditor = class()
function LevelEditor:init()
displayMode(OVERLAY)
displayMode(FULLSCREEN)
self.selectedPiece = "s"
self.pieceInstances = {}
self.nToP = {}
self.nToP[0] = " "
for id, piece in pairs(pieces) do
table.insert(self.pieceInstances, piece())
self.nToP[#self.pieceInstances] = id
--parameter.action(id, function() self.selectedPiece = id end)
end
self.map = {}
self:reset()
parameter.integer("rows", 4, 10, 5, function(r) self:setRows(r) end)
parameter.integer("cols", 3, 10, 4, function(c) self:setCols(c) end)
parameter.action("Export", function() self:export() self.finished = true displayMode(FULLSCREEN) end)
parameter.action("Quit (Lose Level)", function() self.finished = true displayMode(FULLSCREEN) end)
end
function LevelEditor:reset()
self.finished = false
self.map = {}
self.rows, self.cols = 0, 0
self:setRows(5)
self:setCols(4)
end
function LevelEditor:nTpT()
local n = {}
for foo, t in ipairs(self.map) do
table.insert(n, {})
for bar, im in ipairs(t) do
table.insert(n[#n], self.nToP[im])
end
end
return n
end
function LevelEditor:export()
local map = self:nTpT()
local pname = math.floor(self.cols) .. " x " .. math.floor(self.rows)
local foundpack = false
for id, pack in ipairs(levelPacks) do
if pack.name == pname then
table.insert(pack.levels, map)
foundpack = true
end
end
if not foundpack then
table.insert(levelPacks, { name = pname, levels = { map } })
end
saveProjectTable("levelPacks", levelPacks)
end
function LevelEditor:setRows(r)
if (self.rows or 0) < r then
while #self.map < r do
local nt = {}
for _ = 1, self.cols do
table.insert(nt, 0)
end
table.insert(self.map, nt)
end
elseif (self.rows or 0) > r then
while #self.map > r do
table.remove(self.map, #self.map)
end
end
self.rows = r
end
function LevelEditor:setCols(c)
if (self.cols or 0) < c then
for r, ct in ipairs(self.map) do
while #ct < c do
table.insert(ct, 0)
end
end
elseif (self.cols or 0) > c then
for r, ct in ipairs(self.map) do
while #ct > c do
table.remove(ct, #ct)
end
end
end
self.cols = c
end
function LevelEditor:draw()
local size = vec2(WIDTH / self.cols, HEIGHT / self.rows)
for r = 1, self.rows do
local y = HEIGHT + (size.y / 2) - (size.y * r)
for c = 1, self.cols do
local x = (-size.x / 2) + (size.x * c)
if self.map[r][c] == 0 or self.map[r][c] == nil then
strokeWidth(5) rectMode(CENTER)
stroke(0) noFill()
rect(x, y, size.x, size.y)
end
if self.map[r][c] ~= 0 and self.map[r][c] ~= nil then
self.pieceInstances[self.map[r][c]]:draw(x, y, size.x, size.y)
end
if r == 1 and c == 1 then
fill(204, 0, 255, 255) noStroke()
ellipse(x, y, size.x / 3)
end
end
end
end
function LevelEditor:touched(t)
local size = vec2(WIDTH / self.cols, HEIGHT / self.rows)
for r = 1, self.rows do
local y = HEIGHT + (size.y / 2) - (size.y * r)
for c = 1, self.cols do
local x = (-size.x / 2) + (size.x * c)
if t.x > x - size.x / 2 and t.x < x + size.x / 2 and t.y > y - size.y / 2 and t.y < y + size.y / 2
and t.state == ENDED then
self.map[r][c] = (self.map[r][c] + 1) % (#self.pieceInstances + 1)
end
end
end
end
--# Piece
Piece = class()
function Piece:init()
self.colour = color(0, 187, 255, 255)
self.rotation = 0
self.invertWidthHeight = false
end
function Piece:open()
return "yes"
end
function Piece:makeImage(width, height)
self.image = image(math.max(width, height), math.max(width, height))
setContext(self.image)
pushMatrix() resetMatrix()
self:setScene(self.image.width / 2, self.image.height / 2)
self:drawPiece(width, height)
popMatrix()
setContext()
self.lastWidth, self.lastHeight = width, height
end
function Piece:drawImage(x, y, width, height)
sprite(self.image, x, y)
end
function Piece:draw(x, y, width, height)
pushStyle()
resetStyle()
pushMatrix()
local w, h
if self.invertWidthHeight then
w, h = height, width
else
w, h = width, height
end
if w ~= self.lastWidth or h ~= self.lastHeight then
self:makeImage(w, h)
end
self:drawImage(x, y)
popMatrix()
popStyle()
end
function Piece:setScene(x, y)
translate(x, y)
rotate(self.rotation)
end
function Piece:drawPiece(width, height)
fill(self.colour) rectMode(CORNER)
rect(-width / 2, -height / 2, width / 3, height / 3)
rect(-width / 2, -height / 2 + height * 2/3, width / 3, height / 3)
rect(-width / 2 + width * 2/3, -height / 2, width / 3, height / 3)
rect(-width / 2 + width * 2/3, -height / 2 + height * 2/3, width / 3, height / 3)
end
function Piece:touched(touch)
-- Codea does not automatically call this method
end
--# Pieces
Vertical = class(Piece)
function Vertical:init()
Piece.init(self)
end
function Vertical:canMoveIn(coord)
return coord.x == 0
end
function Vertical:canMoveOut(coord)
return coord.x == 0
end
function Vertical:drawPiece(width, height)
fill(self.colour) rectMode(CORNER)
rect(-width / 2, -height / 2, width / 3, height)
rect(-width / 2 + width * 2/3, -height / 2, width / 3, height)
end
Horizontal = class(Vertical)
function Horizontal:init()
Vertical.init(self)
self.rotation = 90
self.invertWidthHeight = true
end
function Horizontal:canMoveIn(coord)
return coord.y == 0
end
function Horizontal:canMoveOut(coord)
return coord.y == 0
end
BottomLeft = class(Piece)
function BottomLeft:init(x)
Piece.init(self)
end
function BottomLeft:canMoveIn(coord)
return (coord.x == 0 and coord.y == -1) or (coord.x == -1 and coord.y == 0)
end
function BottomLeft:canMoveOut(coord)
return (coord.x == 0 and coord.y == 1) or (coord.x == 1 and coord.y == 0)
end
function BottomLeft:drawPiece(width, height)
fill(self.colour) rectMode(CORNER)
rect(-width / 2, -height / 2, width / 3, height)
rect(-width / 2, -height / 2, width, height / 3)
rect(-width / 2 + width * 2/3, -height / 2 + height * 2/3, width / 3, height / 3)
end
BottomRight = class(BottomLeft)
function BottomRight:init(x)
BottomLeft.init(self)
self.rotation = 90
self.invertWidthHeight = true
end
function BottomRight:canMoveIn(coord)
return (coord.x == 0 and coord.y == -1) or (coord.x == 1 and coord.y == 0)
end
function BottomRight:canMoveOut(coord)
return (coord.x == 0 and coord.y == 1) or (coord.x == -1 and coord.y == 0)
end
TopLeft = class(Piece)
function TopLeft:init()
Piece.init(self)
end
function TopLeft:canMoveIn(coord)
return (coord.x == 0 and coord.y == 1) or (coord.x == -1 and coord.y == 0)
end
function TopLeft:canMoveOut(coord)
return (coord.x == 0 and coord.y == -1) or (coord.x == 1 and coord.y == 0)
end
function TopLeft:drawPiece(width, height)
fill(self.colour) rectMode(CORNER)
rect(-width / 2, -height / 2, width / 3, height)
rect(-width / 2, -height / 2 + height * 2/3, width, height / 3)
rect(-width / 2 + width * 2/3, -height / 2, width / 3, height / 3)
end
TopRight = class(TopLeft)
function TopRight:init(x)
TopLeft.init(self)
self.rotation = 270
self.invertWidthHeight = true
end
function TopRight:canMoveIn(coord)
return (coord.x == 0 and coord.y == 1) or (coord.x == 1 and coord.y == 0)
end
function TopRight:canMoveOut(coord)
return (coord.x == 0 and coord.y == -1) or (coord.x == -1 and coord.y == 0)
end
AllSides = class(Piece)
function AllSides:init()
Piece.init(self)
end
function AllSides:canMoveIn(coord)
return true
end
function AllSides:canMoveOut(coord)
return true
end
Goal = class(AllSides)
function Goal:init(x)
AllSides.init(self)
self.colour = color(21, 255, 0, 255)
end
pieces = { }
pieces["h"] = Horizontal
pieces["v"] = Vertical
pieces["bl"] = BottomLeft
pieces["br"] = BottomRight
pieces["tl"] = TopLeft
pieces["tr"] = TopRight
pieces["s"] = AllSides
pieces["f"] = Goal
--# Utilities
-- Converts table to string then stores in local data
function saveLocalTable(name, tbl)
saveLocalData(name, tableToString(name, tbl))
end
-- Converts table to string then stores in project data
function saveProjectTable(name, tbl)
saveProjectData(name, tableToString(name, tbl))
end
-- Turns a table into a string for data storage
function tableToString(name, value, saved)
local function basicSerialize (o)
if typeOf(o) == "number" then
return tostring(o)
elseif typeOf(o) == "boolean" then
return tostring(o)
else -- assume it is a string
return string.format("%q", o)
end
end
saved = saved or {}
local returnStr = name.." = "
if typeOf(value) == "number" or typeOf(value) == "string" or typeOf(value) == "boolean" then
returnStr = returnStr .. basicSerialize(value).."\n"
elseif typeOf(value) == "vec2" then
returnStr = returnStr .. "vec2(" .. value.x .. ", " .. value.y .. ")\n"
elseif typeOf(value) == "table" then
if saved[value] then
returnStr = returnStr .. saved[value].."\n"
else
saved[value] = name
returnStr = returnStr.."{}\n"
for k,v in pairs(value) do
local fieldname = string.format("%s[%s]", name, basicSerialize(k))
returnStr = returnStr .. tableToString(fieldname, v, saved)
end
end
else
error("Cannot save a " .. typeOf(value))
end
return returnStr
end
-- Extended type function
function typeOf(x)
if x == nil then
return 'nil'
end
if type(x) == 'table' and x.is_a then
return('class')
end
local txt
if typeTable == nil then
typeTable = {
[getmetatable(vec2()).__index ] = 'vec2',
[getmetatable(vec3()).__index ] = 'vec3',
[getmetatable(color()).__index ] = 'color',
[getmetatable(image(1,1)).__index ] = 'image',
[getmetatable(matrix()).__index] = 'matrix',
[getmetatable(mesh()).__index ] = 'mesh' ,
[getmetatable(physics.body(CIRCLE, 1)).__index] = 'physics body',
}
end
local i = getmetatable(x)
if i then
txt = typeTable[i.__index]
end
if txt then
return txt
end
txt = type(x)
return txt
end
-- Formats seconds into Minutes:Seconds Format (00:00)
function formatTime(seconds)
local fMins = string.format("%02.f", math.floor(seconds / 60))
local fSecs = string.format("%02.f", math.floor(seconds - (fMins * 60)))
local formattedTime = fMins .. ":" .. fSecs
return formattedTime
end
-- Same as a % b, except that if a is a multiple b it returns b not 0
function specialModulo(a, b)
local mod = b * (a/b - math.floor(a/b))
if mod == 0 then
mod = b
end
return mod
end
-- Returns the sign of a number (-1 for negative or 1 for positive)
math.sign = function(v)
if v < 0 then
return -1
else
return 1
end
end
-- Same as math.sign but returns 0 if number is 0
math.signOrZero = function(v)
if v < 0 then
return -1
elseif v > 0 then
return 1
else
return 0
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment