Ink, 120 119 116 bytes
=f(n)
->g(n,3,2)
=g(n,b,s)
~n--
{n:{~{b:b->g(n,b-1,s)}|{s:s->g(n,b,s-1)}|}f->g(n,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-- // Decrement remaining length
{
- length: // 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,balls_left-1,strikes_left)}|{strikes_left:s->g(length,balls_left,strikes_left-1)}|}f->g(length,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}}->->
Edits
- Saved a byte by finishing with
->->
instead of->END
. - Saved three bytes by decrementing
n
earlier.