# Chaos is an ASCII ladder

You know nothing The things I do for "Chaos is a ladder" is a memorable line from the television series Game of Thrones.

The purpose of this challenge is to build a ladder from chaos, in ASCII art.

# The challenge

### Input

• Ladder width, W >= 3 (integer)
• Step height, H >= 2 (integer)
• Number of rungs, N >= 2 (integer).

### Output

A ladder with horizontal rungs and vertical rails, all 1 character wide. Ladder width (W) includes the two rails, and step height (H) includes the corresponding rung.

All rungs, including the uppermost and the lowermost, will have a piece of vertical rail of length H-1 directly above and below. The example will make this clearer.

The ladder will be made of printable, non-whitespace ASCII characters, that is, the inclusive range from ! (code point 33) to ~ (code point 126).The actual characters will be chosen randomly. Given the inputs, each of the random choices of characters must have nonzero probability. Other than that, the probability distribution is arbitrary.

Leading or trailing whitespace, either horizontal or vertical, is allowed.

### Example

Given W=5, H=3, N=2, one possible output is as follows.

x   :
g   h
q$UO{ t T 6 < bUZXP 8 T 5 g  Note that total height is H*(N+1)-1, as there are N rungs and N+1 vertical sections. # Aditional rules • Input means and format are flexible as usual. For example, you can input the three numbers in any order, or an array containing them. • Output may be through STDOUT or an argument returned by a function. In this case it may be a string with newlines, a 2D character array or an array of strings. • A program or a function can be provided. • Standard loopholes are forbidden. • Shortest code in bytes wins. # Test cases For each W, H, N a possible output is shown. W=5, H=3, N=2: \ ~ : K ke:[E 5 u 0 _ 8Fr.D # r 7 X W=3, H=2, N=2:$ X
Mb)
0 ]
(T}
j 9

W=12, H=4, N=5:

d          Y
P          
5          3
p$t$Ow7~kcNX
D          x
O
*          H
LB|QX1'.[:[F
p          p
x          (
2          ^
ic%KL^z:KI"^
C          p
(          7
7          h
TSj^E!tI&TN8
|          [
<          >
=          Q
ffl^,tBHk?~
O          +
p          e
n          j

W=20, H=5, N=3:

G                  %
o                  y
%                  3
-                  7
U'F?Vml&rVch7{).fLDF
o                  }
U                  I
h                  y
a                  g
;W.58bl'.iHm\8v?bIn&
,                  U
N                  S
4                  c
5                  r
F3(R|<BP}C'$=}xK$F]^
'                  h
h                  u
x                  $6 5  • can you add the (numerical) range for the ascii characters? – Rod Sep 14 '17 at 14:45 • @Rod Good idea. Done – Luis Mendo Sep 14 '17 at 14:51 • What kind of lower limit on the quality of randomness is there? I assume starting at a random point and incrementing modulo (126-33) wouldn't qualify because of obvious correlation between adjacent values. Or does it have to be capable of producing every possible sequence? (So an 8-bit linear congruential generator wouldn't work, because one character uniquely determines the next character?) – Peter Cordes Sep 14 '17 at 23:50 • @PeterCordes There is no problem with having some correlation, as long as every possible combination can occur. The approach you describe is, as you say, not valid because it introduces too strong statistical dependence between characters at different positions, making some combinations impossible – Luis Mendo Sep 15 '17 at 0:01 • @PeterCordes Yes, I meant theoretically. Don't worry about RNG strength; you can assume the RNG is ideal. There's some meta consensus about that. I thought there was something more specific, but all I could find was this and this – Luis Mendo Sep 15 '17 at 0:41 ## 24 Answers # Jelly, 24 23 22 bytes «þỊoU$ẋ⁵‘¤Ḋ×94X€€+32ỌY


A full program taking the three arguments W, H, N and printing the result.

Try it online!

### How?

Builds a 2d array mask of a single rung and its vertical sections below, repeats it N+1times and removes the top rung then places random characters or spaces depending upon the mask value.

«þỊoU$ẋ⁵‘¤Ḋ×94X€€+32ỌY - Main link: W, H (N is third input / 5th command line argument) þ - outer product (build a table using): « - minimum - ...note: þ implicitly builds ranges of W and H prior Ị - insignificant? (abs(z)<=1) - yields a W by H 2-d array, - all zeros except the left and top edges which are 1s$                 - last two links as a monad:
U                  -   upend (reverse each row)
o                   -   or (vectorises) - giving us our |‾| shape of 1s
⁵               -   5th command line argument, N
‘              -   increment -> N+1
ẋ                - repeat list - giving us our ladder-mask plus a top rung)
Ḋ            - dequeue - remove the top rung
94         - literal ninety-four
×           - multiply (vectorises) - replace the 1s with 94s
X€€      - random for €ach for €ach - 0 -> 0; 94 -> random integer in [1,94]
32   - literal thirty-two
+     - add (vectorises) - 0 -> 32; random integers now from [33,126]
Ọ  - character from ordinal (vectorises)
Y - join with newlines
- implicit print


# Operation Flashpoint scripting language, 643 624 bytes

f={l=["""","!","#","$","%","&","'","(",")","*","+",",","-",".","/","0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?","@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","[","\","]","^","_","","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","{","|","}","~"];t=_this;w=t select 0;r={l select random 92};v="";s=v;i=2;while{i<w}do{i=i+1;v=v+" "};p={i=1;while{i<t select 1}do{i=i+1;s=s+call r+v+call r+"\n"}};k=0;call p;while{k<t select 2}do{k=k+1;i=0;while{i<w}do{i=i+1;s=s+call r};s=s+"\n";call p};s}  Ridiculously long because there is no way to create the characters from the character codes. Call with: hint ([5, 3, 2] call f)  Output: The ladder is extra chaotic because the font is not monospaced. Unrolled: f = { l = ["""","!","#","$","%","&","'","(",")","*","+",",","-",".","/","0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?","@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","[","\","]","^","_","","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","{","|","}","~"];

t = _this;
w = t select 0;

r =
{
l select random 92
};

v = "";
s = v;

i = 2;
while {i < w} do
{
i = i + 1;
v = v + " "
};

p =
{
i = 1;
while {i < t select 1} do
{
i = i + 1;
s = s + call r + v + call r + "\n"
}
};

k = 0;
call p;
while {k < t select 2} do
{
k = k + 1;

i = 0;
while {i < w} do
{
i = i + 1;
s = s + call r
};
s = s + "\n";

call p
};

s
}

• It seems as "\n" works, "\xa3" to get something like £ doesn't work? if you can use unicode escapes you might be able to trim that array of yours. – Tschallacka Sep 15 '17 at 9:45
• How can I run this myself? :D – Geeky I Sep 15 '17 at 10:22
• This just looks like a script was written and the spaces and newlines were removed. Is it only getting upvotes because of an image of a ladder or have I missed some clever golfing? – Jonathan Allan Sep 15 '17 at 10:51
• @steadybox are you using contextual screenshots now after I requested a non-grassland screenshot on that one question xD? – Magic Octopus Urn Sep 15 '17 at 20:15
• @Tschallacka \n is the only escape that is recognized. (And "" inside quotes to represent one ") – Steadybox Sep 15 '17 at 21:58

# 05AB1E, 29 bytes

Input taken in the order N, H, W

>*GNUžQ¦©.RIÍFð®.R«X²Öè}®.RJ,


Try it online!

Explanation

>*G                              # for N in [1 ... (N+1)*H)-1] do:
NU                            # store N in variable X
žQ                          # push a string of printable ascii
¦©                        # remove the first (space) and save a copy in register
.R                      # pick a random character
IÍF                   # W-2 times do:
ð                  # push a space
®.R               # push a random ascii character
«              # concatenate
X²Ö           # push X % H == 0
è          # index into the string of <space><random_char> with this
}         # end inner loop
®.R      # push a random ascii character
J,    # join everything to a string and print

• This is a bit cheating. – vdegenne Sep 14 '17 at 23:43
• @user544262772 why so? – Jonathan Allan Sep 15 '17 at 5:08
• may you explain this a little bit? – Mischa Sep 15 '17 at 8:35
• @MischaBehrend: Sure, I've added an explanation now. – Emigna Sep 15 '17 at 8:41
• @user544262772 it can be quite challenging to make a well golfed answer in a golfing language, believe me, they are designed for golfing, but using them usually requires some thought (unless it's just "here's a built-in that does exactly what you want"). – Jonathan Allan Sep 15 '17 at 10:48

# C, 95 bytes

f(w,h,n,i){++w;for(i=0;i++<w*~(h*~n);)putchar(i%w?~-i%w%(w-2)*((i/w+1)%h)?32:33+rand()%94:10);}


# R, 13812911198 93 bytes

-13 bytes thanks to Neal Fultz!

-1 byte thanks to Robin Ryder

function(W,H,N){m=matrix(intToUtf8(32+sample(94,W*(h=H*N+H-1),T),T),h)
m[-H*1:N,3:W-1]=" "
m}


Try it online!

Anonymous function; returns the result as a matrix.

Thanks to that Word Grids question, I've been thinking about matrices a lot more than usual. I observed that the rungs are in those matrix rows that are a multiple of the step height H (R is 1-indexed), and that the rails are the first and last columns, 1 and W. So I create a matrix of random ASCII characters, and replace those letters that didn't match those criteria with spaces, and return the matrix. TIO link prints it out nicely.

Neal Fultz suggested a different indexing for the space characters, [-H*(1:N),3:W-1], which replaces all characters except for those in rows of multiples of H: -H*(1:N) and not on the edge, 3:W-1 <==> 2:(W-1).

# R, 121 bytes

function(W,H,N)for(i in 1:(H*N+H-1)){for(j in 1:W)cat("if"(!(i%%H&j-1&j-W),sample(intToUtf8(33:126,T),1)," "))
cat("\n")}


Try it online!

An improvement over the original matrix-based approach I started with; it's the same algorithm but for loops are shorter than constructing and printing a matrix (but not if I don't print it!)

• m[-H*(1:N),3:W-1]=" " seems a little shorter - you can always replace testing row and col with a 2-d slice. – Neal Fultz Sep 19 '17 at 4:59
• @NealFultz wow, that's quite excellent! Thank you! – Giuseppe Sep 19 '17 at 13:13
• -1 byte by replacing sample(33:126,...) with 32+sample(94,...). – Robin Ryder May 18 '19 at 17:15

## Perl 5, 81 bytes

80 bytes code + 1 for -p.

/ \d+ /;$_=(($}=(_.$"x($-2)._.$/)x($&-1))._ x$.$/)x$'.$};s/_/chr 33+rand 94/ge


Try it online!

• the space after \d+ may be removed because of greedy match – Nahuel Fouilleul Sep 15 '17 at 9:29

# Charcoal, 34 32 bytes

Ｅ…¹×⁺¹ＮＮ⪫ＥＩζ⎇∧﹪ιＩη﹪λ⁻Ｉζ¹ §⮌γ‽⁹⁴ω


Try it online! Takes input in the order N, H, W. Verbose approximation (Plus(InputNumber(), 1) is currently broken on TIO). Explanation:

Ｅ…¹×⁺¹ＮＮ


Map over the range 1..H*(N+1). This means that the rungs appear when i is a multiple of H.

⪫


Join the result of:

ＥＩζ


mapping over the implicit range 0..W:

⎇∧﹪ιＩη﹪λ⁻Ｉζ¹


if the column is not 0 or W-1 and the row is not a multiple of H then output a space;

§⮌γ‽⁹⁴


otherwise, take the predefined ASCII character variable, reverse it (putting the space in 94th place), and print a random character from what is now the first 94. (Because Slice sucks.)

ω


• Not sure if it's helpful but you could draw the ladder and then peekall and map over random printable I think? EDIT It appears to be broken. Oops. – ASCII-only Sep 15 '17 at 13:29
• I'll try to fix it (pretty sure it was working before) but I've been a bit busy so it might take a while – ASCII-only Sep 15 '17 at 13:37
• @ASCII-only I assume you're thinking of ＮθＧＨ↓θ→Ｎ↑θ*‖Ｏ↓Ｆ⁻Ｎ¹Ｃ⁰θ¿ＥＫＡ§⮌γ‽⁹⁴«? Well, I bisected and commit a0a6316 broke it. – Neil Sep 20 '17 at 19:54
• @ASCII-only Actually that's not quite true, there was an unrelated bug in Map where it used not is_command when it meant is_command. So you were supposed to write ＮθＧＨ↓θ→Ｎ↑θ*‖Ｏ↓Ｆ⁻Ｎ¹Ｃ⁰θＵＭＫＡ§⮌γ‽⁹⁴ if it hadn't been for that bug. – Neil Sep 20 '17 at 20:01

# C (gcc), 141131114109 107 bytes

Should be able to golf this down quite a bit...

i,j,c;f(w,h,n){for(i=1;i<h*n+h;i+=j==w)printf(i%h?i++,j=0,"%c%*c\n":"%c",++j^w?c^8:10,w-2,c=33+rand()%94);}


Try it online!

• can we edit i=1 in the global declaration? – Mukul Kumar Sep 16 '17 at 6:06
• 106 bytes – ceilingcat Oct 30 '20 at 1:37

# Perl 6, 76 73 bytes

->\h,\n,\w{map {chrs (roll(w,1..94)Z*1,|($_%%h xx w-2),1)X+32},1..^h*n+h}  Try it online! Takes (h, n, w) as arguments. Returns a list of strings. Explanation: -> \h, \n, \w { # Block taking arguments h, n, w map { # String from codepoints chrs # Generate w random numbers between 1 and 94 (roll(w, 1..94) # Set inner numbers on non-rungs to zero Z* 1, |($_%%h xx w-2), 1)
X+ 32
}
# Map h*n+h-1 row numbers (1-indexed)
1..^h*n+h
}

• Alternative 73 byter using xx and ++$ instead of map. Maybe you can find a place to golf off any bytes? – Jo King Nov 5 '18 at 11:27 # PowerShell, 132 124 bytes param($w,$h,$n)-join([char[]]((($a=('#'+' '*($w-2)+"#n")*--$h)+'#'*$w+"n")*$n+$a)|%{($_,[char](33..126|Random))[$_-eq35]})


Try it online!

We construct a ladder composed of only # first (example), then loop |%{...} through each character and if it's -equal to 35, we pull out a new Random character from the appropriate range. Otherwise we output (i.e., either a space or newline).

# JavaScript (ES6), 117 115 bytes

A recursive function building the output character by character.

"Look Ma, no literal line-feed!"

(w,h,n)=>(g=x=>y<h*n+h-1?String.fromCharCode(x++<w?x%w>1&&-~y%h?32:Math.random()*94+33|0:10)+g(x>w?!++y:x):'')(y=0)


### Demo

let f =

(w,h,n)=>(g=x=>y<h*n+h-1?String.fromCharCode(x++<w?x%w>1&&-~y%h?32:Math.random()*94+33|0:10)+g(x>w?!++y:x):'')(y=0)

O.innerText = f(5,3,4)
<pre id=O></pre>

• Damnit! I was in the process of golfing mine down when I saw this. :\ You win again! :p – Shaggy Sep 15 '17 at 13:41
• I finished golfing mine down (for now) - I drew the line at moving all the ternaries within String.fromCharCode, as I couldn't honestly say I'd come up with that myself after seeing this. Let me know if you feel mine is now too similar to yours. – Shaggy Sep 15 '17 at 15:32
• @Shaggy No worries! (Actually, I saw your answer only after posting mine. If I had seen it earlier, I would probably have given up.) – Arnauld Sep 15 '17 at 15:42
• Ah, it's the nature of the game! :) Combining our solutions gives 113 bytes, by the way – Shaggy Sep 15 '17 at 16:45

# Python 2, 142 bytes

lambda w,h,n,e=lambda:chr(randint(33,126)):[e()+[eval(("e()+"*(w-2))[:-1])," "*(w-2)][-~i%h>0]+e()for i in range(h*-~n-1)]
from random import*


Try it online!

Saved bytes thanks to ovs!

• @LuisMendo I think I fixed it now. – Mr. Xcoder Sep 14 '17 at 15:16
• @LuisMendo It's my fault for not checking carefully. – Mr. Xcoder Sep 14 '17 at 15:18
• 42 bytes – ovs Sep 17 '17 at 21:28
• @ovs Thanks! You forgot a 1 in the front though >_> – Mr. Xcoder Sep 17 '17 at 21:29

# Pyth, 33 bytes

VhEjtW!Nmsm?&d}kr1tQ\ Or\!C127Qvz


Try it online: Demonstration

# Python 2, 114 bytes

lambda w,h,n:[[chr(32+randint(1,94)*(x%~-w*(y%h)<1))for x in range(w)]for y in range(1,h*-~n)]
from random import*


Try it online!

# SOGL V0.12, 32 31 bytes

 ~ΔkψR
I{e{R}¶bH{Re⁾⌡@R¶}}¹∑e⌡k


Try it Here!

Input in the order N, W, H.

Explanation:

 ~ΔkψR

R  a function named "R", pushes a random character:
~       push "~"
Δ      get the charasters from " " to "~"
k     remove the 1st character
ψ    choose a random character from that

I{e{R}¶bH{Re⁾⌡@R¶}}¹∑e⌡k  main program

I                         increase the 1st input - N
{                }       N times do
e                         push the variable e, which is here initialised to the next input - W
{ }                      E times do
R                         execute R
¶                     push a newline
bH                   push b-1, where b is initialised to the next input - H
{       }          B-1 times do
R                   execute R
e⁾                 push e-2 aka width-2
⌡@               push that many spaces
R              execute R
¶             push a newline
¹∑     join the stack together
e⌡k  remove the first width characters


18 bytes without the random characters :/

# Java 8, 203188168133132130128 126 bytes

W->H->N->{for(double i=0,j,q;++i<H*N+H;)for(j=W,q=10;j-->=0;q=i%H*j<1|j>W-2?33+Math.random()*94:32)System.out.print((char)q);}


Try it online!

• 133 bytes: W->H->N->{for(int i=0,j;i++<H*N+H-1;){char c=10;for(j=W;j-->0;c=i%H<1|j>W-2|j<2?(char)(Math.random()*94+33):32)System.out.print(c);}} – Nevay Sep 14 '17 at 15:58
• You are currently printing the result for W-1, the inner loop has to do an additional iteration (>=0 +1 byte). – Nevay Sep 14 '17 at 16:09
• 132 bytes: W->H->N->{for(int i=0,j;i++<H*N+H-1;){char c=10;for(j=W;j-->=0;c=i%H*j<1|j>W-2?(char)(Math.random()*94+33):32)System.out.print(c);}} – Nevay Sep 14 '17 at 16:10
• ;++i<H*N+H; : -2 bytes. – Olivier Grégoire Sep 15 '17 at 9:24

import System.Random
a=mapM id
b=(putStr.unlines=<<).a
c=randomRIO('!','~')
r w=a$c<$[1..w]
s w=a$c:(return ' '<$[3..w])++[c]
(w#h)0=b$s w<$[2..h]
(w#h)n=do{b$(s w<$[2..h])++[r w];(w#h)$n-1}  Try it online! Saved 9 bytes thanks to Laikoni Saved 21 bytes thanks to wchargin Should be golfable (b$(s w)<$[2..h] and b$((s w)<$[2..h])++[r w]). I don't feel comfortable with IO and golfing. • You can use infix notation for t w h n= ...: (w#h)n= .... pure can be used instead of return. You can drop the parentheses around (d ' ')<$and (s w)<. – Laikoni Sep 14 '17 at 20:36 • @Laikoni I won't forget the infix notation next time! Thanks. – jferard Sep 15 '17 at 15:43 • Save a bunch of bytes with c=randomRIO('!','~'), which also lets you inline d=return. Also, mapM id is one byte shorter than sequence. – wchargin Sep 17 '17 at 20:35 • @wchargin Thanks. I learned something today! – jferard Sep 18 '17 at 16:05 # JavaScript (ES6), 144 bytes (w,h,n)=>Array(n+1).fill(("#".padEnd(w-1)+# ).repeat(h-1)).join("#".repeat(w)+ ).replace(/#/g,_=>String.fromCharCode(33+Math.random()*94|0))  Creates the ladder out of # characters then replaces each one with a random ASCII char. ## Test Snippet let f= (w,h,n)=>Array(n+1).fill(("#".padEnd(w-1)+# ).repeat(h-1)).join("#".repeat(w)+ ).replace(/#/g,_=>String.fromCharCode(33+Math.random()*94|0)) ;(W.oninput=H.oninput=N.oninput=B.onclick=_=>{let vals=[W.value,H.value,N.value].map(eval);D.innerText=vals.join, ;O.innerText=f(...vals);})() W: <input id=W type=range min=3 max=25 value=12><br> H: <input id=H type=range min=2 max=25 value=5><br> N: <input id=N type=range min=2 max=10 value=2><br> <button id=B>Rerun</button> <code id=D></code> <pre id=O></pre> • String.fromCharCode and Math.random in one solution - why does JavaScript hate us?! Came up with this for 137 bytes, which is very similar to yours, just without the array. I wonder now if a recursive solution might be shorter still; will investigate later. – Shaggy Sep 15 '17 at 10:12 • Knocked another byte off that – Shaggy Sep 15 '17 at 16:49 # JavaScript (ES6), 129 117 bytes Unfortunately, while I was in the process of golfing this down, Arnauld beat me to a similar but shorter solution. By combining our 2 solutions, this can be 113 bytes Includes a trailing newline. (w,h,n)=>(g=c=>l?(c++<w?c%w>1&&l%h? :String.fromCharCode(94*Math.random()+33|0): )+g(c>w?!--l:c):)(0,l=h*++n-1)  ## Try it o.innerText=(f= (w,h,n)=>(g=c=>l?(c++<w?c%w>1&&l%h? :String.fromCharCode(94*Math.random()+33|0): )+g(c>w?!--l:c):)(0,l=h*++n-1) )(i.value=5,j.value=3,k.value=2);oninput=_=>o.innerText=f(+i.value,+j.value,+k.value) label,input{font-family:sans-serif;font-size:14px;height:20px;line-height:20px;vertical-align:middle}input{margin:0 5px 0 0;padding:0 0 0 5px;width:100px;} <label for=i>W: </label><input id=i min=3 type=number><label for=j>H: </label><input id=j min=2 type=number><label for=k>N: </label><input id=k min=2 type=number><pre id=o> # Japt-R, 424140373428 25 bytes Takes input in the order H,W,N. ;*°WÉ ÆJ²ùVXgJùU¹r,@EÅöÃé  Try it # QBIC, 76 bytes [:*:+b-1|G=chr(_r33,126|)~a%b\[:-2|G=G+@ ]][e|G=G+chr$(_r33,126|)]?_sG,1,e  ## Explanation [ FOR a = 1 TO : input 1 (Height, now in var b) * times : input 2 (# of rungs, now in var c) +b-1| plus one bottom rung without crossbar G=chr$(_r33,126|)       Assign to G a random char (_r is the RAND() function, chr$() is BASIC's num-to-char) ~a%b| IF we are not at a crossbar (the modulo returns anything but 0), [:-2|G=G+@  add to G x spaces, where x is width (input 3, now 'e') - 2 Note that G is now either 'X' or 'X ' (for rnd char X and W=5) ]] Close the spacing FOR, close the IF [e| FOR f = 1 to width G=G+chr$(_r33,126|)]     Append to G a rnd char
G is now either 'XXXXXX'  or 'X   XXXXX' (for rnd char X and W=5)
?_sG,1,e                PRINT the first w characters of G, and on to the next line


## Sample run

Command line: 3 2 5
N   F
M
Bj#=y
!   (
S   N
q(.Ho
%   7
g   ,


# MATL, 63 50 bytes

-13 bytes thanks to Luis Mendo

Q*qXJ*6Y2HY)wT3$ZrJ3G3$eJ3G&Ol5LZ(J:HG\~3GTX"!+g*c


Try it online!

I'm still new to golfing in MATL (and I'm not super good at MATLAB for that matter), so I know this probably isn't close to optimal. Tips are welcome. Takes input in order N,H,W.

Here we go:

Q*qXJ                     # compute H*(N+1)-1, store as J
*                    # multiply by W
6Y2HY)              # push printable ASCII
wT3$Zr # sample uniformly with replacement J3G3$e  # reshape to make a matrix of the appropriate shape.


We now have a matrix of random char.

J3G                       # push J,W
&O                     # zero matrix, J x W
l5LZ(                # assign 1 to first and last columns


Now there's also a logical matrix for the rails.

J:                        # push J, range, so 1...J
HG                      # take second input (H)
\~                    # mod and bool negate (so it's 1 for rows of multiples of H)
3GTX"!              # repmat and transpose so we have 1's for rungs


Now we have 3 matrices on the stack:

• Top: 0 for non-rung, 1 otherwise
• Middle: 0 for non-rail, 1 otherwise
• Bottom: random characters, -20

So we do the following:

+                         # add the top two matrices.
g                        # convert to logical so 0->0, nonzero->1
*                      # elementwise multiply
c                     # convert to char, implicit output (0 -> space).

• Here are a few quick tips: X" is 3$ by default. 6Y2 may be handy instead of 13:106...20+. ~~ is g. J3G&Ol5LZ( can be used instead of 1F3G2-Y"h1hJT3$X" – Luis Mendo Sep 18 '17 at 21:36
• @LuisMendo Ah, I didn't quite make it through all the docs or I would have discovered that about X". In that last tip, 5L is [1 0] but I'm not sure how that's used in conjunction with Z( -- I get that it's assigning 1 to the first and last columns but I don't get how 5LZ( accomplishes that. I'll probably ping you in MATL CHATL sometime later about this so don't worry about that for now. – Giuseppe Sep 18 '17 at 22:37
• Indexing is modular, so 0 is the same as "end". Z( assigns to columns. Of course, feel free to ping me in chat! – Luis Mendo Sep 18 '17 at 23:03

# Powershell, 102 bytes

param($w,$h,$n)1..(++$n*$h-1)|%{$l=$_%$h
-join(1..$w|%{[char](32,(33..126|Random))[!$l-or$_-in1,$w]})}


Less golfed test script:

$f = { param($w,$h,$n)
1..(++$n*$h-1)|%{       # for each lines of the ladder
$l=$_%$h # line number in a step -join(1..$w|%{      # make a line
[char](32,(33..126|Random))[!$l-or$_-in1,$w] }) # a random character if the line number in a step is a rung line or char position is 1 or width # otherwise a space } } &$f 5 3 2
&$f 3 2 2 &$f 12 4 5
&$f 20 5 3  Output: 0 { H S ']UxR G ] 3 t q^R8O q y t J U h YQZ _ i 3#D I # = m & < ] 6 8nmuyw2'Y7%+ o l ; ! D M Fn[zGfT";RYt @ B$          e
z          @
@J[1|:-IS~y<
(          L
:          [
|          q
zBow0T0FnY8)
/          *
e          B
R          p
9{d2(RacBdRj
u          ~
          l
J          h
v                  t
T                  -
v                  H
'                  Y
IS7{bx2&k@u7]o}>[Vq?
F                  U
?                  U
|                  Q
}                  T
:wv1wEfc6cS;430sigF|
<                  L
:                  }
*
H                  =
L8k5Q/DQ=0XIUujK|c6|
j                  =
!                  p
V                  :
#                  w


# Ruby, 71 bytes

EDIT: Oops I thought this was a new challenge because of the recent edit to fix a typo lol. I'm still leaving this up though because there's no Ruby answer for it yet.

->w,h,n{(1..h*-~n-1).map{|i|[*?!..?~].sample(x=i%h>0?2:w)*(' '*(w-x))}}


Try it online!