Last active
April 16, 2019 07:18
-
-
Save tarrouye/44c541318700bbe3c687 to your computer and use it in GitHub Desktop.
GAMEJAM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# 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