# Ladder Man

As a fun exercise I decided to make a clone to Timberman. Of course, making a straight up clone would be kind of lame, so I took the gameplay and I put it in a different scenario.

Ladder Man - you tap on one side of the screen to climb the ladder on that side. if you climb into a gap, you fall and die. if you hold on to one rung for too long, you fall and die.

Here are some screenshots, what do you guys think?

@JakAttak Looks fun, I’d play it anyway! Are you planning on publishing it or just doing it for practice/fun? Also, as a sidebar question, are you using sprites/meshes for your graphics or making them out of shapes?

@Staples, I don’t have plans to publish it, I might but it is doubtful. I may release the code (though it is kind of messy because it was just a playing around) as an example for newbies.

The graphics are all from platformer art by the incredibly talented kenny.nl

Here’s the code as it stands if you just wanna give it a go:

``````
--# Main
-- Ladderman

displayMode(FULLSCREEN)
supportedOrientations(PORTRAIT)
function setup()
PLAYING, LOST = 1, 2
MODE = PLAYING

stuffs()
initialise()
end

function draw()
background(255)

if MODE == PLAYING or MODE == LOST then
drawGame()
end

if MODE == LOST then
drawLost()
end
end

function touched(t)
if t.state == ENDED then
if MODE == PLAYING then
touchPlaying(t)
elseif MODE == LOST then
touchLost(t)
end
end
end

--# Playing
-- Just holds most of the functions for the playing state

-- Resets the game
function initialise()
ladders = {}
player = { x = rightX, y = sizes.ladder.y * 3.5, iid = 1, angle = 0 }
sign = { x = 0, y = sizes.ladder.y + sizes.sign.y / 2, alpha = 0 }

for r = 1, math.ceil(HEIGHT / sizes.ladder.y) + 1 do
addRow(true)
end

groundO = sizes.ladder.y
score = 0
highscore = readLocalData("highscore") or 0
time = .5
if timeT ~= nil then tween.stop(timeT) end
canMove = true
end

-- Draws everything for the playing state
function drawGame()
-- Draw the 'bulding' background
for bi = #ladders, 1, -2 do
sprite(imgs.brick, WIDTH / 2, ladders[bi].y, WIDTH, sizes.ladder.y)
end

-- Draw the ladders
for li = #ladders, 1, -1 do
local ladder = ladders[li]
if not ladder.hole then
sprite(imgs.ladder, ladder.x, ladder.y, sizes.ladder.x, sizes.ladder.y)
end
end

-- Draw the player
pushMatrix() translate(player.x, player.y) rotate(player.angle)
sprite(imgs.player[player.iid], 0, 0, sizes.player.x, sizes.player.y)
popMatrix()

-- Draw the ground if it is still onscreen
if groundO < sizes.ladder.y then
sprite(imgs.ground, WIDTH / 2, sizes.ladder.y / 2 - groundO, WIDTH, sizes.ladder.y)
end

-- Draw the timer bar
local o = WIDTH / 200
local a = (rightX - sizes.ladder.x / 2) - (leftX + sizes.ladder.x / 2)
sprite(imgs.bar[1], WIDTH / 2, HEIGHT - HEIGHT / 40, a, HEIGHT / 20)
sprite(imgs.bar[2], WIDTH / 2, HEIGHT - HEIGHT / 40, (a - o*1.8) * time, HEIGHT / 20 - o*1.8)

-- Draws the score and then the highscore right below it
sprite(imgs.panel, WIDTH / 2, HEIGHT / 2, a / 1.5)
font(GAMEFONT)
fill(0) if score >= highscore then fill(255,0,0) end fontSize(WIDTH / 10)
text(score, WIDTH / 2, HEIGHT / 2)
fill(255,0,0) fontSize(WIDTH / 20)
text(math.max(highscore, score), WIDTH / 2, HEIGHT / 2 - fontSize() * 1.5)
fill(0)
text("SCORE", WIDTH / 2, HEIGHT / 2 + fontSize() * 1.5)
stroke(0, 155, 255, 153) strokeWidth(oneS * 2)
line(WIDTH / 2 - a / 3.27, HEIGHT / 2 - fontSize(), WIDTH / 2 + a / 3.27, HEIGHT / 2 - fontSize())
line(WIDTH / 2 - a / 3.27, HEIGHT / 2 + fontSize(), WIDTH / 2 + a / 3.27, HEIGHT / 2 + fontSize())

-- Lost / died sign
tint(255, sign.alpha)
sprite(imgs.sign, sign.x, sign.y, sizes.sign.x, sizes.sign.y)
noTint()
end

-- Losing sequence
function fall()
canMove = false

if timeT ~= nil then tween.stop(timeT) end

tween(.75, _G, { groundO = 0 })
falling = tween(.75, player, { y = 0, angle = 230 }, tween.easing.linear, function()
sign.x = player.x + sizes.ladder.x / 2 + sizes.sign.x / 1.75
if player.x == leftX then sign.x = player.x - sizes.ladder.x / 2 - sizes.sign.x / 1.75 end
falling = tween(.5, sign, { alpha = 255 }, tween.easing.linear, function()
animateEndMenu()
MODE = LOST
end)
end)
end

-- Adds another row to the top of the ladder
function addRow(rnok)
local y
if #ladders == 0 then y = sizes.ladder.y / 2
else y = ladders[#ladders].y + sizes.ladder.y end

local rand = math.random(1, 5)
local rhole, lhole = false, false
if not rnok then
if rand == 2 and lastRand ~= 3 then rhole = true
elseif rand == 3 and lastRand ~= 2 then lhole = true end
end
lastRand = rand

table.insert(ladders, { x = rightX, y = y, hole = rhole })
table.insert(ladders, { x = leftX, y = y, hole = lhole })
end

-- Checks if the player is in a hole
function checkLoss()
for li = #ladders, 1, -1 do
local ladder = ladders[li]
if ladder.hole and ladder.x == player.x and math.abs(ladder.y - player.y) < sizes.ladder.y / 2 then
if score > highscore then
saveLocalData("highscore", score)
end

fall()
return true
end
end

return false
end

-- Moves ladders and player, and calls to check if player is in a hole
function moveObjects(right)
canMove = false
player.iid = 2

if right then
player.x = rightX
else
player.x = leftX
end

checkLoss()

for li = #ladders, 1, -1 do
local ladder = ladders[li]
tween(0.04, ladder, { y = ladder.y - sizes.ladder.y })

if ladder.y < 0 then
table.remove(ladders, li)
end
end

tween.delay(0.04, function()
addRow()

if not checkLoss() then
player.iid = 1

score = score + 1
canMove = true
end
end)
end

-- Adds a bit of time to the timer
function addToTimer()
if timeT ~= nil then
tween.stop(timeT)
timeT = nil
end

time = math.min(time + .04, 1)
timeT = tween(time * 8, _G, { time = 0 }, tween.easing.linear, fall)
end

-- All the touch code for the playing state
function touchPlaying(t)
if canMove then
moveObjects(t.x > WIDTH / 2)
addToTimer()
end
end

--# Lost
-- Just holds most of the functions for the lost state
function animateEndMenu()
tween(.5, replay, { y = HEIGHT / 2 })
end

function animateHideEndMenu()
tween(.5, replay, { y = HEIGHT + sizes.replay.y / 2 }, tween.easing.linear, function()
initialise()
MODE = PLAYING
end)
end

function drawLost()
sprite(imgs.panel, replay.x, replay.y, sizes.replay.x, sizes.replay.y)
stroke(0, 155, 255, 153) strokeWidth(oneS * 2)
local lof, sof, tof = sizes.replay.x / 2.17, sizes.replay.y / 4, sizes.replay.y / 9
line(replay.x - lof, replay.y - sof, replay.x + lof, replay.y - sof)
line(replay.x - lof, replay.y + sof, replay.x + lof, replay.y + sof)

fill(0) fontSize(WIDTH / 8)
text("GAME OVER", replay.x, replay.y + sof + tof)
text("PLAY AGAIN?", replay.x, replay.y - sof - tof)
end

function touchLost(t)
if t.x >= replay.x - sizes.replay.x / 2 and t.x <= replay.x + sizes.replay.x / 2
and t.y >= replay.y - sizes.replay.y / 2 and t.y <= replay.y - sizes.replay.y / 4 then
animateHideEndMenu()
end
end

--# Variables
function stuffs()
GAMEFONT = "Futura-CondensedMedium"

oneS = WIDTH / 768

local bimg, gimg = makeImgs()
imgs = {
ladder = readImage("Dropbox:ladder_mid"),
player = { readImage("Dropbox:alienGreen_climb1"), readImage("Dropbox:alienGreen_climb2") },
ground = gimg,
sign = readImage("Dropbox:signRip"),
brick = bimg,
bar = { readImage("Dropbox:barHorizontal_shadow_dark_full"), readImage("Dropbox:barHorizontal_red_full") },
panel = readImage("Dropbox:glassPanel_corners")
}

sizes = {
ladder = vec2(WIDTH / 9, HEIGHT / 9),
player = vec2(WIDTH / 10, HEIGHT / 9.5),
sign = vec2(WIDTH / 8, HEIGHT / 9),
replay = vec2(WIDTH / 1.5, WIDTH / 1.5)
}

replay = { x = WIDTH / 2, y = HEIGHT + sizes.replay.y / 2 }

leftX = WIDTH / 3.5
rightX = WIDTH * 2.5/3.5
end

function makeImgs()
local brickImg = image(WIDTH, HEIGHT / 9) setContext(brickImg)
for i = 1, 9 do
sprite("Dropbox:stoneCenter", -WIDTH / 18 + (WIDTH / 9) * i, HEIGHT / 18, WIDTH / 9, HEIGHT / 9)
end
setContext()

local groundImg = image(WIDTH, HEIGHT / 9) setContext(groundImg)
for i = 1, 9 do
sprite("Dropbox:grassMid", -WIDTH / 18 + (WIDTH / 9) * i, HEIGHT / 18, WIDTH / 9, HEIGHT / 9)
end
setContext()

return brickImg, groundImg
end
``````

You will have to use placeholder sprited though… (they are all defined in the variables tab, under fairly clear names so you could pick something that fits)

I plan to add a formal menu screen at the beginning and redo the replay screen as I’m not super pleased with it.

thank you @jakattak.
Once again, the images have to be replaced…
That make me wonder… what about creating a public repo for shared game images? Maybe we could use some already existing web service? twitter, or else? The equivalent of gist on github.

@JakAttak @Jmv38 you could share your images in a link with your code using Dropbox, imgur, etc.
NEW IDEA: there could be one public shared Codea folder on Dropbox for people to upload their images (though this may be very memory consuming).

@JakAttak thanks for sharing the code. Though it looks a bit wonky with my replacement images, it works well. Nice job!

Well done! I’ll try the code as soon as I can

@Staples what would be the api for sharing via dropbox? Could you post a working example? Each time i tried from the ipad, i could not even get a usable link from propbox (it goes through their touch interface first, instead of accessing directly the image)

Yes, I have code to download images to Dropbox, guess its time to break it out

@ignatz wow, great post thanks a lot! PhotoBucket seems great, do you know how to send there an image from codea?

The user and all related content has been deleted.

scroll down and you’ll see some toffer magic

@NatTheCoder that’s because you don’t have the images used by @JakAttak. You have to replace them with default images built in to Codea for the game to run.

The user and all related content has been deleted.

Added image loading code, redid replay screen, move player down a rung.

Enjoy!

i’ve just tried your code, but i get

``````error: [string "Loader = class()..."]:91: attempt to perform arithmetic on local 'b' (a nil value)
``````

@Jmv38, so sorry about that, I guess I already had the images so did not do a ‘full’ test. Let me have a look.

EDIT: I found the problem: sneaky Dropbox changed the site code on me

I have updated the code in the gist, so this: https://gist.github.com/JakAttak/cce08f9c984ed81440dd should work now.

Thanks!

@JakAttak This brings back memories of a game I played on my Apple II. It was called Lode Runner. There is an app called Lode Runner classic that looks just like what I played.