Table of Contents

Examples

This page contains practical examples demonstrating how to use libtwee for common tasks.

Basic Story Creation

Creating a Story from Scratch

using libtwee;

// Create a new story
var story = new Story
{
    Name = "My Interactive Fiction",
    IFID = Babel.GenerateTwineIFID(),
    Format = "Harlowe",
    FormatVersion = "3.3.5"
};

// Add passages
story.AddPassage(new Passage("Start", "Welcome to the adventure!\n\n[[Begin->Forest]]"));
story.AddPassage(new Passage("Forest", "You are in a dark forest.\n\n[[Go deeper->Cave]]\n[[Turn back->Start]]"));
story.AddPassage(new Passage("Cave", "You discovered a mysterious cave!\n\nThe End."));

// Set the starting passage
story.Start = "Start";

// Convert to HTML
string html = story.ToTwine2HTML();
File.WriteAllText("my-story.html", html);

File Format Conversions

Twee to HTML

// Read Twee file
string tweeContent = File.ReadAllText("story.twee");

// Parse and convert
Story story = Twee.Parse(tweeContent);
string html = story.ToTwine2HTML();

// Save HTML
File.WriteAllText("story.html", html);

Twee to JSON

// Convert Twee to Twine 2 JSON format
string tweeContent = File.ReadAllText("story.twee");
Story story = Twee.Parse(tweeContent);
string json = story.ToTwine2JSON();

File.WriteAllText("story.json", json);

HTML to JSON

// Parse existing Twine 2 HTML
string htmlContent = File.ReadAllText("story.html");
Story story = Twine2HTML.Parse(htmlContent);

// Convert to JSON
string json = story.ToTwine2JSON();
File.WriteAllText("converted-story.json", json);

Working with Story Data

Extracting Story Information

string tweeContent = @"
:: StoryTitle
The Lost Temple

:: StoryData
{
  ""ifid"": ""12345678-1234-5678-9ABC-123456789ABC"",
  ""format"": ""Harlowe"",
  ""format-version"": ""3.3.5"",
  ""start"": ""Entrance""
}

:: Entrance
You stand before an ancient temple.
[[Enter->Hall]]

:: Hall  
The hall is dimly lit by torches.
[[Explore->Chamber]]
[[Leave->Entrance]]

:: Chamber
You found a treasure chest!
";

Story story = Twee.Parse(tweeContent);

Console.WriteLine($"Title: {story.Name}");
Console.WriteLine($"IFID: {story.IFID}");
Console.WriteLine($"Format: {story.Format} {story.FormatVersion}");
Console.WriteLine($"Starting passage: {story.Start}");
Console.WriteLine($"Total passages: {story.Count}");

// List all passages
foreach (var passage in story.Passages)
{
    Console.WriteLine($"- {passage.Name}: {passage.Text.Length} characters");
}

Adding Metadata

var story = new Story();

// Set basic metadata
story.Name = "Adventure Game";
story.IFID = Babel.GenerateTwineIFID();
story.Creator = "Twine";
story.CreatorVersion = "2.8.1";

// Add tag colors
story.TagColors["important"] = "red";
story.TagColors["optional"] = "blue";

// Add stylesheets
story.StoryStylesheets.Add("body { background-color: #333; color: white; }");

// Add scripts
story.StoryScripts.Add("console.log('Story loaded');");

Advanced Passage Manipulation

Finding and Modifying Passages

Story story = Twee.Parse(File.ReadAllText("story.twee"));

// Find a specific passage
var startPassage = story.GetPassage("Start");
if (startPassage != null)
{
    // Modify passage content
    startPassage.Text += "\n\n[[New option->Secret]]";
    
    // Add tags
    startPassage.Tags.Add("modified");
}

// Add a new passage
var secretPassage = new Passage("Secret", "You found a secret area!");
secretPassage.Tags.Add("hidden");
story.AddPassage(secretPassage);

Working with Passage Tags

string tweeWithTags = @"
:: Start [beginning important]
This is the start of the story.

:: Forest [location outdoor]
You are in a forest.

:: Cave [location indoor dark]
A dark cave entrance.
";

Story story = Twee.Parse(tweeWithTags);

// Find passages with specific tags
var locationPassages = story.Passages
    .Where(p => p.Tags.Contains("location"))
    .ToList();

Console.WriteLine("Location passages:");
foreach (var passage in locationPassages)
{
    Console.WriteLine($"- {passage.Name}: {string.Join(", ", passage.Tags)}");
}

// Find outdoor locations
var outdoorPassages = story.Passages
    .Where(p => p.Tags.Contains("outdoor"))
    .ToList();

IFID Management

Generate and Validate IFIDs

// Generate a new IFID
string newIFID = Babel.GenerateTwineIFID();
Console.WriteLine($"Generated IFID: {newIFID}");

// Validate IFIDs
string[] testIFIDs = {
    "12345678-1234-5678-9ABC-123456789ABC", // Valid
    "invalid-ifid",                         // Invalid
    "12345678-1234-5678-9ABC-123456789abc", // Valid (mixed case)
    ""                                      // Invalid (empty)
};

foreach (string ifid in testIFIDs)
{
    bool isValid = Babel.IsValidTwineIFID(ifid);
    Console.WriteLine($"IFID '{ifid}': {(isValid ? "Valid" : "Invalid")}");
}

Ensuring Story Has Valid IFID

Story story = Twee.Parse(tweeContent);

// Check if story has a valid IFID
if (string.IsNullOrEmpty(story.IFID) || !Babel.IsValidTwineIFID(story.IFID))
{
    // Generate a new IFID if missing or invalid
    story.IFID = Babel.GenerateTwineIFID();
    Console.WriteLine($"Assigned new IFID: {story.IFID}");
}
else
{
    Console.WriteLine($"Story has valid IFID: {story.IFID}");
}

Error Handling

Robust Parsing with Error Handling

public static Story SafeParse(string content, string filename)
{
    try
    {
        Story story = Twee.Parse(content);
        
        // Validate the parsed story
        if (story.Count == 0)
        {
            throw new InvalidOperationException("Story contains no passages");
        }
        
        if (string.IsNullOrEmpty(story.Start))
        {
            // Set first passage as start if not specified
            story.Start = story.Passages.First().Name;
            Console.WriteLine($"Warning: No start passage specified, using '{story.Start}'");
        }
        
        return story;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error parsing {filename}: {ex.Message}");
        throw;
    }
}

// Usage
try
{
    string content = File.ReadAllText("story.twee");
    Story story = SafeParse(content, "story.twee");
    Console.WriteLine("Story parsed successfully!");
}
catch (Exception ex)
{
    Console.WriteLine($"Failed to process story: {ex.Message}");
}

Batch Processing

Converting Multiple Files

public static void ConvertTweeToHTML(string inputDirectory, string outputDirectory)
{
    var tweeFiles = Directory.GetFiles(inputDirectory, "*.twee");
    
    foreach (string tweeFile in tweeFiles)
    {
        try
        {
            string content = File.ReadAllText(tweeFile);
            Story story = Twee.Parse(content);
            
            string filename = Path.GetFileNameWithoutExtension(tweeFile);
            string outputPath = Path.Combine(outputDirectory, $"{filename}.html");
            
            string html = story.ToTwine2HTML();
            File.WriteAllText(outputPath, html);
            
            Console.WriteLine($"Converted: {tweeFile} -> {outputPath}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error converting {tweeFile}: {ex.Message}");
        }
    }
}

// Usage
ConvertTweeToHTML("./twee-stories", "./html-stories");