HWYDT: Ladders
Tags: miniscript, minimicro, programming, howto
Published: 2024-01-17 17:05:31

[Last week](https://dev.to/joestrout/hwydt-donkey-kong-29me), we looked at how you would do sloped platforms like in Donkey Kong.  People loved it, but they wanted to know:


![Something comes to mind ... might be easy, but maybe not obvious - and at least an interesting problem: climbing up ladders. How to detect when there is one, how to animate going up / down, and when (and how) to stop and be on the ground again.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gt25lyh0f38aht418glz.png)


![I was just about to ask this very same question about the ladders. :-) I'd love to see a follow-up post here.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lw3nyn4ag4d7d1xdnw4.png)

## How Would You Do That: Ladders?

So it turns out that the Platformer class in /sys/lib/spriteControllers already handles most of the messy details of climbing.  If you define a climb animation, then by default your character can climb anywhere.  We defined just such an animation in our little demo:

```
kip.climbAnim = newAnim([
   file.loadImage("/sys/pics/KP/KP-climb1.png"),
   file.loadImage("/sys/pics/KP/KP-climb2.png")], 10)
```

Try it!  If you don't have last week's code handy, you can twist open this section and copy it into [Mini Micro](https://miniscript.org/MiniMicro/):

{% details Last week's code (click the little triangle to see) %}
```
import "spriteControllers"

clear
display(5).mode = displayMode.tile
td = display(5)
td.tileSet = file.loadImage("dk_tiles.png")
td.tileSetTileSize = 8
td.cellSize = 32
td.extent = [28, 32]
td.clear

makeFlat = function(x0, x1, y)
	for x in range(x0, x1)
		td.setCell x, y, 16
	end for
end function

makeRampUp = function(x0, x1, y0, startLevel)
	level = startLevel
	for x in range(x0, x1)
		td.setCell x, y0, 32 - level
		if level > 0 then td.setCell x, y0+1, 40 - level
		if abs(x-x0) % 2 == 0 then continue
		level += 1
		if level > 7 then
			y0 += 1
			level = 0
		end if
	end for
end function

// Set up the level
makeFlat 0, 13, 0
makeRampUp 14, 27, 0, 1
makeRampUp 25, 0, 3, 4
makeRampUp 2, 27, 7, 5
makeRampUp 25, 0, 11, 6
td.setCell 10, 1, 8  // (ladder)

// Set our player character, "Kip"
newAnim = @spriteControllers.newAnimation
kip = new spriteControllers.Platformer
display(4).sprites.push kip
kip.x = 400; kip.y = 80
kip.idleAnim = newAnim(file.loadImage("/sys/pics/KP/KP-stand.png"))
kip.runAnim = newAnim([
   file.loadImage("/sys/pics/KP/KP-run1.png"),
   file.loadImage("/sys/pics/KP/KP-run2.png")], 10)
kip.jumpUpAnim = newAnim(file.loadImage("/sys/pics/KP/KP-jump.png"))
kip.fallDownAnim = kip.jumpUpAnim
kip.climbAnim = newAnim([
   file.loadImage("/sys/pics/KP/KP-climb1.png"),
   file.loadImage("/sys/pics/KP/KP-climb2.png")], 10)
kip.curAnim = kip.idleAnim

// determine whether (and where) kip has solid ground below him
kip.groundBelow = function(x,y)
	col = floor(x / 32)
	row = floor(y / 32)
	while true
		if row < 0 then return 0
		tile = td.cell(col, row)
		if 16 <= tile <= 23 or 32 <= tile <= 39 then
			return (row+1) * 32 - (tile % 8) * 4
		end if
		row = row - 1
	end while
end function

while true
	spriteControllers.updateSprites 1/60
	yield
end while
```
{% enddetails %}

`run` that code, and then just press the up-arrow key anywhere.  You'll find Kip happily climbs up (and down) nothingness.

![screen shot of Kip climbing nothing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vq07b02a5h62lcc9thv6.png)

## Adding ladders

Last week we hacked in a single ladder tile, just for decoration.  This week, we want more!  Again we'll copy the original _Donkey Kong_ layout.

`edit` the program, and scroll down to right below the `makeRampUp` function (right above `// Set up the level`).  Insert this new function to create a ladder:

```
makeLadder = function(x, y0, y1=null)
	if y1 == null then y1 = y0
	for y in range(y0, y1)
		c = td.cell(x, y)
		if 24 <= c <= 39 then
			td.setCell x, y, c - 16
		else
			td.setCell x, y, 8
		end if
	end for
end function
```

This function takes the X (horizontal) position of the ladder, and one or two Y (vertical) positions for the ladder extent.  It iterates over that Y range, and sets a ladder.  But what exactly is going on with that `if` statement?

To understand that, we need to refer back to our tile set:

![tile indexes for dk_tiles.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zkveah1oh363mv3cczpe.png)

You can see that tiles 24 through 39 are the partial-girder tiles, places where we have just the top or bottom of a girder (in order to make sloped platforms).  How do we modify those to have a ladder?  As the tile set is arranged, the proper girder+ladder section is always 16 tile indexes before the girder-only tile.  So, if we find the existing cell is in this range, we subtract 16.  If the existing cell is anything else, then we just set it to index 8, which is a full ladder piece.

Now replace the next part of code, which sets up the level, with this:

```
// Set up the level
makeFlat 0, 13, 0
makeRampUp 14, 27, 0, 1
makeRampUp 25, 0, 3, 4
makeRampUp 2, 27, 7, 5
makeRampUp 25, 0, 11, 6
makeLadder 10, 1; makeLadder 10, 4
makeLadder 23, 1, 3
makeLadder 12, 5, 8
makeLadder 4, 5, 7
makeLadder 8, 9; makeLadder 8, 12
makeLadder 14, 9, 12
makeLadder 23, 9, 11
```

When you run this, you should see a more complete level with ladders, as shown below.

![Level with ladders](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0g9pv1g910asfhmulg3b.png)


## Climbing on ladders only

To limit where our character can climb, we need to override the `canClimb` method on our character class.  To prove that this works, `edit` your code, scroll down below the `kip.groundBelow` function (right above the `while true` loop), and insert this code:

```
kip.canClimb = function(direction)
	return false
end function
```

Now `run` the program again, and you should find that Kip can't climb at all (even though he still has a climb animation defined).

So the job of this method is to return `true` when kip can climb, given his current location and the intended climb direction (+1 to climb up, or -1 to climb down).

Now if we didn't have partial-girder, partial-ladder tiles, and if ladders extended through the girders, then implementing this function would be fairly trivial: we'd only need to check if the tile at our feet is the ladder tile.  But neither of these things is the case; ladders in Donkey Kong stop _below_ the girder they attach to, and because of slopes, we have to deal with funny Y coordinates depending on  which particular tile we have.  All that makes our `canClimb` method a bit longer.  But here it is:

```
kip.canClimb = function(direction)
	col = floor(self.x / 32)
	row = floor((self.y - self.footOffset) / 32)
	c = td.cell(col, row)
	if (c == null or c < 8 or c > 23) and (
		direction < 0 or self.state == spriteControllers.CLIMBING) then
		// no ladder here, if either we're going down, or we're 
		// already climbing, then also check the tile right below it
		row -= 1
		c = td.cell(col, row)
	end if
	if c == null or c < 8 or c > 23 then return false
	// OK, there's a ladder in this cell; but account for slopes
	// to figure out exactly where it ends.
	climbStep = self.climbSpeed / 60
	if 16 <= c <= 23 and direction < 0 then  // ladder on top of a girder
		minY = self.groundBelow(self.x, self.y)
		if self.y - self.footOffset < minY + climbStep then return false
	else if 9 <= c <= 15 and direction > 0 then	// ladder below a girder
		maxY = self.groundBelow(self.x, (row + 1) * 32)
		if self.y - self.footOffset + climbStep*2 > maxY then return false
	end if
	// center ourselves horizontally on the ladder
	self.x = (col + 0.5) * 32
	return true
end function
```

Let's walk through how this works.  First, we get the `col`umn and `row` of our feet, and check the cell at that position.  If it's not a ladder cell (between 8 and 23), _and_ if we're either going down or we're already climbing, then we check the tile below.  That deals with the case at the top of a ladder, where we're actually on a girder tile but we still want to consider it climbable.

Then, we have two `if` blocks that make adjustments for slopes (partial girders).  The first one, checking for a tile index from 16 to 23, deals with a ladder section on top of a girder (which occurs at the bottom of a ladder).  We make use of the `groundBelow` function from last week to figure out where the actual girder part is, in pixel coordinates.  If our climb down would exceed that, we report false ("can't climb").

The `else if` block, checking for a tile index from 9 to 15, handles the opposite: this is the girder on top of the ladder, and again we use `groundBelow` (starting up one row) to figure the maximum Y we should allow the player to climb.  

The final part of this method just centers the player on the ladder.  You could comment this out if you want to see the effect: without it, you can climb up the side of a ladder, which looks a little funny.

This method was developed iteratively, which is to say, I coded a little, tested a little, and repeated until I was satisfied.  That's likely to be the case in your own game, too — getting the feel just right often takes a little experimentation.

## That's all, folks!

And that's it!  Once you have that `canClimb` method properly defined for your game, the library code handles the rest.  You should now be able to run, jump, and climb around your level!

![Animated GIF of Kip climbing around our Donkey Kong level](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9louwhmggkcmd09fqd9m.gif)

As you can see, making a run/jump/climb platformer in Mini Micro is much easier than you'd probably expect (even with sloped platforms!).  Why not try making your own game?

Also, I'd like to continue to explore how classic video game effects were done, all on hardware considerably less powerful than Mini Micro.  So keep your eye out for things that make you go "hmm," and let me know in the comments below if anything makes you say: **How would you do that?!?**