Log In  


I've been playing around with various procedural generation techniques and wrote a little tool for creating generative string grammars. It's 104 tokens and the code is on github under an MIT license.

Demos

This first one generates side-scroller/platformer levels. Each character of the generated string represent a four tile wide column of the map. I didn't add a player because I just wanted to showcase the level generation itself so the map just scrolls automatically from the start to the end. Reload the cart to generate a new level. It's just an example so the levels aren't particularly interesting but you could add a few "post-production" passes over the generated strings to clean things up, generate enemies, add additional rules for pre-made features, etc.

Cart #jdelaat_stringgram_scroller-0 | 2021-06-21 | Code ▽ | Embed ▽ | No License
1

The next one is a vastly simplified version of this process to generate names of things/places in a made-up language. The grammar constructs some syllables then makes words of either two or three syllables. A final pass over the generated word cleans up some messy, hard-to-pronounce double consonants. For a quick, not very sophisticated implementation it comes up with almost but not quite decent sounding place names.

Cart #jdelaat_stringgram_words-0 | 2021-06-21 | Code ▽ | Embed ▽ | No License
1

API

string-gram provides a number of functions to help you build a grammar and it's easily extensible via custom rule functions. Grammar rules are just functions which take no arguments and return a string.

  1. lit

    The lit function takes a string as input and produces a rule which will generate that string when called:

h = lit('hello') -- h is a rule, aka a function of 0 arguments.
print(h()) -- prints 'hello'
  1. seq

    The seq function takes any number of rules as input and produces a new rule which outputs the result of each rule in sequence.

h = lit('hello')
c = lit(', ')
w = lit('world')
hw = seq(h, c, w)
print(hw()) -- prints 'hello, world'
  1. choice

    The choice function takes any number of rules as input and outputs a new rule which outputs the result of one of those rules chosen at random.

a = lit('a')
b = lit('b')
ab = choice(a, b)
print(ab()) -- prints either 'a' or 'b' at random
  1. copy

    The copy function takes a single rule and an integer, n as input and outputs a new rule which applies the given rule n times.

a = lit('a')
aaa = copy(a, 3)
print(aaa()) -- prints 'aaa'
  1. sym and register

    Grammars are often defined recursively so you may find yourself needing to include one rule, which you haven't yet defined, inside the definition of some other rule. sym and register solve this problem by allowing you to insert a 'symbolic' rule which will be looked up at a later time when it's actually called.

one = lit('1')

-- This won't work!
-- many_ones = choice(one, seq(one, many_ones))

-- Instead, we use a symbolic rule...
many_ones = choice(one, seq(one, sym('1s')))

-- ...and then use register to insert the rule into a lookup table.
register('1s', many_ones)

print(many_ones()) -- prints random number of 1s
                   -- ex: 1, 111, 1111111111111, etc
  1. Custom rules

    Rules are just functions which take no arguments and return a string so you can easily create your own rules or functions which create rules. For instance, suppose you want to create a rule that randomly returns the output from another rule or else returns an empty string. One way to do that would be like so:

function zero_or_one(rule)
   return function()
      if rnd() > 0.5 then
	 return rule()
      else
	 return ''
      end
   end
end

a = lit('a')
a_or_not = zero_or_one(a)
print(a_or_not()) -- prints either one 'a' or nothing with 50/50 probability
1



[Please log in to post a comment]