Story Generator

Generate stories from descriptions written in Story Description Language (see below)!

Install

Story Generator is a Ruby Gem, so simply install it with gem install story-gen. Of course you also need Ruby >= 1.9.3!

Usage

story file.sdl

Read story description from "file.sdl", generate a story and write it to stdout.

story -c -n MyStory -o file.rb file.sdl

Read story description from "file.sdl" and write a class MyStory to "file.rb". The class MyStory subclasses Story (see docs) and can be used like this: MyStory.new.write().

story --help

More help on story.

Story Description Language (SDL)

A simple story:

"Hello, world!"

It just prints "Hello, world!". To print newline just write newline or nl:

"Hello, world!" newline
"Another line of helloness!" nl

Or you may insert newline inside the quotes:

"
Chapter I
---------
"

You may use single quotes as well:

'Hello, world!'

Comments:

"Hello, world!" (note: a comment, "literal" style)
/* a comment, C style */

A story is based on Facts. To state a Fact just write it:

"John" loves "Liza"

In the Fact expression you may use english and russian words (except keywords, see below), characters from "#№@$%/-,[]{}", integer numbers (e.g., 18) and arbitrary double- or single-quoted strings ("..." or '...'). Trailing commas are ignored (xxx yyy zzz, is the same as xxx yyy zzz). The words are case-insensitive (so loves and Loves mean the same), quoted strings are case-sensitive (so "John""JOHN").

Let's state some Facts:

"John" is a boy;
"Sam" is a boy;
"Liza" is a girl;
"Sabrina" is a girl;
"John" loves "Liza";

Statements in the story are separated with a semicolon (";"). The semicolon is optional if its absence does not cause an ambiguity, so the following is valid too:

"John" is a boy;
"Sam" is a boy;
"Liza" is a girl;
"Sabrina" is a girl;
"John" loves "Liza"

What can Facts be used for? You may form conditions with them:

If X is a boy then "Let's meet " X;

Or, with a comma (",") before then:

If X is a boy, then "Let's meet " X;

Here X is a variable. Variable names consist of underscores ("_") and capital letters only (e.g., LONG_VARIABLE_NAME). You may only capture quoted strings or numbers as variables, so the following is invalid:

If "John" A "Liza" then ...  /* ERROR! */

You may use the captured variables in the statement after then keyword.

To print the captured variable you just write it along with some quoted strings and newlines:

"Let's meet " X "." nl;

But wait... We have two boys! What does if choose as X then? The answer is: random. If there are several combinations of variables which fit the condition then a random combination is chosen.

You may form complex Fact expressions using and, or and not keywords and parentheses:

If X is a boy and Y is a girl then X" meets "Y"!"

If (X is a boy) and (Y is a girl) then X" meets "Y"!"

If X is a boy and Y is a girl and not X loves Y then
  X" meets "Y"!"

The not keyword may also be written inside the Fact:

If X is a boy and Y is a girl and X not loves Y then
  X" meets "Y"!"

Not all combinations of and, or and not are available, though. Use common sense to find out which one are. For example, this is an error:

If X is not a boy then "How can I determine "X"?"  /* ERROR! */

You may also compare variables in the condition:

If X is a boy and Y is a boy and X <> Y then
  X" and "Y" are two different boys!"

There are limitations on the comparison: the comparison must be after the and keyword and all variables must be mentioned in the left part of and.

You may use =, !=, <>, =/=, <=, <, > and >= as comparison operators. Take types of the comparands into account, though!

You may use asterisk ("*") instead of the variable to avoid capturing:

If X is a boy and X not loves * then
  X" is a lonely boy."

You may use otherwise keyword:

If X is a boy and X not loves * then
  X" is a lonely boy."
otherwise
  X" has found someone!"

You may combine several conditions and then statements using colon (":") and dashes ("-"):

If:
  - X is a boy then "We have found a boy "X;
  - Y is a girl then "We have found a girl "Y;
  - otherwise "We do not know anyone";

This is like a classical if ... else if ... else ... but if multiple conditions are true then the random one is chosen (instead of the first one, like in the classical if-else). The last otherwise is the same as else in the classical if-else - it is chosen if all other conditions are false.

Look at the trailing semicolons (";") by the way - they resolve ambiguity!

Another form of if:

If:
  a) X is a boy then "We have found a boy "X;
  b) Y is a girl then "We have found a girl "Y;
  c) otherwise "We do not know anyone";

You may use captured variables to state a Fact:

If X is a boy and Y is a girl then
  X loves Y

You may set the Fact false:

If X loves Y then
  X not loves Y

Set multiple Facts false:

If X is a boy then
  X not loves *

You may use the captured variables in another conditions by prefixing them with hat ("^"):

If X is a boy then
  if ^X loves * then "We found "X" which is in love!"
  otherwise "We found "X" which is stll single."

To combine several statements into one use a colon (":") with the final dot ("."):

If X is a boy then:
  "Let's meet " X "!";
  X " is a boy!".

or parentheses:

If X is a boy then (
  "Let's meet " X "!";
  X " is a boy!";
)

There are other statements you can use:

  • "While":


  While <fact expression> <statement>
  

Here <fact expression> is the same as in if statement except that you may use not in top level:


  While X not loves *:
    If X is a boy and Y is a girl then X loves Y.
  

The variables from <fact expression> are not available in <statement>.

Note that usually there is no way to delimit <fact expression> from <statement> except by wrapping the <statement> into ":" and "." or into parentheses.

  • "Repeat n times":


  10 times "Hello!"         (note: print "Hello!" 10 times)
  10...20 times "Hello!"    (note: random number between 10 and 20 is chosen)
  X times "Hello!"          (note: the value of the captured variable X is used)
  X...Y times "Hello!"      (note: the value between two captured variables
                            is used)
  

  • "For all":


  For all <fact expression> <statement>
  

<statement> is executed for all combinations of variables in <fact expression>. The <fact expression> is like in if statement.

  • Ruby code:


  <code>puts(x); puts(y)</code>
  

Inside the code you can access the captured variables by their lowercase names:


  If X loves Y then
    <code>puts(x); puts(y)</code>
  

  • "Either ... or ...":


  Either <statement> [;|,]
  or <statement> [;|,]
  or <statement> [;|,]
  ...
  or <statement> [;|,]
  

[;|,] means optional character which is either semicolon (";") or comma (",").

A random <statement> is chosen and executed.

  • "When":


  When <fact> <statement>
  

When you try to state the <fact> then <statement> is executed.


  When X loves Y:
    ""X" marries "Y"!";
  .
  "John" loves "Liza";
  

The code above prints "John marries Liza!"

Notes

(note: ...) comment may have nested parentheses:

(note: this is a comment (with nested parentheses)!)

You may end the story with a dot ("."):

"John" loves "Liza";
"Sam" loves "Sophia".

Keywords if, either, or, for (in for all) and while may start with a capital letter: If, Either etc.

You may also use russian keywords! ;)

Examples

See them in "sample" directory (you may get it with story --samples)! Or in YARD doc's "File list"!