# [Ink](https://inklestudios.com/ink/), <s>120</s> 119 bytes

    =f(n)
    ->g(n,3,2)
    =g(n,b,s)
    {n>1:{~{b:b->g(n-1,b-1,s)}|{s:s->g(n-1,b,s-1)}|}f->g(n-1,b,s&&s+1)|{~{b:h|b}|{s:h|s}|h}}->->

Probably still golfable. No online interpreter, sorry.

# Ungolfed (mildly reformatted)
    =f(length) // Define a stitch f, with one parameter which specifies the length of the created string. This is the intended entry point.
    ->g(length,3,2) // Instantly divert to g, defined below, with some extra parameters
    
    =g(length,balls_left,strikes_left) // Define a stitch g, with three parameters.
    {
        - length > 1: // If this is not to be the last character in the string
                      // randomly do one of the following:
                      // 1. If balls_left is nonzero, print a b and recurse
                      // 2. If strikes_left is nonzero, print an s and recurse
                      // 3. Do nothing
                      // If we did not divert earlier, print an f and recurse.
            {~{balls_left:b->g(length-1,balls_left-1,strikes_left)}|{strikes_left:s->g(length-1,balls_left,strikes_left-1)}|}f->g(length-1,balls_left,strikes_left&&strikes_left-1) 
        - else: // Randomly do one of the following
                // 1. If a ball would result in a walk, print a b, otherwise an h.
                // 2. If a strike would result in a strikeout, print an s, otherwise an h.
                // 3. Just print an h.
                // And finally, halt.
            {~{balls_left:h|b}|{strikes_left:h|s}|h}}->->

Edit: Saved a byte by finishing with `->->` instead of `->END`.