Page 1 of 1

CYOA/MBS - Do It Yourself

PostPosted: Fri Apr 20, 2007 3:52 am
by Moonlit Naiad
(mirrored from: Midnight Aurora)

--

So, to do your own version of the Choose Your Own Adventure style story that I did for Love-Love 4/5 you need three things -- an idea, a notepad or something to sketch out story arcs and branches (ah, a use for flowcharting!) and the code.

My code is listed below and is free to use and modify under the terms of the Creative Commons license I released it under. I realize that there are other (and better) ways to do the code (objects and a database would have made much more sense), but due to knowledge and resource limitations I went with JavaScript (which has very poor support for objects).

First, the idea -- generally easy enough to come up with an idea for a story, but it also needs to support places where the story can branch to alternate (not necessarily always good) endings and possibly to support different story arcs or options leading to the same episode. Continuity and canon become a little harder to manage if the arcs mingle. (In the below example, episode 5 is the next day from episode 4, but still the same day if coming in from episdoe 17, thus all reference to previous occurances (the episode 0/intro stuff) could not be refered to as "The fight/girls you saw yestreday")

What I chose was a magical girl story where the protagonist / player character was not yet a girl and the general accepted resolution (for a TF/TG story) is that he ends as part of the team. Of course, there are several ways for a story like that to go, and there are several ways for a player/reader to deviate from the original story concept, allowing for lots of different arcs and endings, a couple "good," many "average," and several "bad."

The simplified plot outline is listed below. When I drew it out on paper, however, rather than merely using numbers I put a brief little blurb of what was supposed to happen there, but to do that in a text document gets a little out of hand
Code: Select all
                  [introduction]
                 /              \
                 1               2                   |==================|
                / \             / \ /<-----------\   |                  |
               3   4           15 16---------\    \  |   x     +        |
              22   5<---\      x /  \        |     | |   -1    -2       |
              x   / \    \-----<17  19       18    | |     \  /  \      |
                 6   7             /  \      | \   | |      -3   -4     |
                /     \           40  41     20 21>/ |                  |
               8       9          x   42    /  \     | -999 = error     |
              / \     / \            /  \  36  37    |   -9 = cheat     |
         /---24 34   23  10         43  44  x   |    |==================|
        /   / \  x   x  /  \        +    x     / \
       25  26  27      11  12                 38 39
      / \   |   x      14  13                  x +
     28 29  |          x   x
      x 35  |
        x  / \
          30 31
         /  \ x
        32  33
         +  x

(17 leads into 5, which otherwise follows 4 as well, and 21 leads back to 16 in a loop)
x = ending, either average or bad
+ = good ending

-1 is the "You've reached an ending, but not the good ending"
-2 is the "you've reached a good ending, here's also links to all the other endings"
-3 is the "advanced credits" page which both endings allow you to see
-4 is the "character info" page which would contain spoilers for people not having seen a good ending, plus it is also used as a reward.
-999 is what I used in the code to link to an ep that hadn't been assigned a number yet (for instance, I had the 2-> 15, 16, 17, 18, 19 drawn up while I was working on the lefthand set of arcs and so many of those episodes temporarily linked to -999
-9 technically is not a page number, but a default that is shown for anytime an invalid page number is passed to the code, typically done by someone editing the url and changing the number to get to a different episode.

Episode numbering was changed from draft to final to prevent ease of jumping from what ep to another without following the story.

Code follows in a subsequent post.

PostPosted: Fri Apr 20, 2007 3:53 am
by Moonlit Naiad
First the set-up
Code: Select all
<html>

<script language="JavaScript">
// ************************************************************
// * Based on the code from Love-Love 4/5 by Autumn M Winters *
// * http://www.midnightaurora.com/mbs/ll45.html              *
// *                                                          *
// * Released under Creative Commons License                  *
// * http://creativecommons.org/licenses/by-nc-sa/2.5/deed.en *
// *                                                          *
// * Sharing / reposting / derivatives okay if                *
// * non-commericial, attribution, and same license used      *
// ************************************************************

// precursors and convenience section
  var branch=0; // set to default

// ********************************************************************
// ** if there is a serach string, get it and evaluate it, otherwise **
// ** use the default set above                                      **
// ********************************************************************
if (document.location.search.length !=0)
  {
   var query=document.location.search;
   query= query.substr(1,query.length);
   eval(query);                                 
  } /* for convenience */
function wr(theString)
{
document.write(theString);
return;
}; function refresh()
{
    window.location.reload( false );
} function br()
{ wr("<br>"); return; }
/* end convenience functions */
</script>

The credit/attribution/license comment or one that contains fundamentally the same information must be included in any derivative work.

var branch=0; can be set to any number but it must be the number used for the first branch of the story. If you load the page as just the url (ie: http://sub.site.tld/path/filename.html) it tells the code to display this episode.

The subsequent if statement tells it to pick out the substring (the ?branch=####;) statement and evaluate that, replacing the default set above.

The convenience functions br() and wr() are because I got tired of having to document.write every single time anything is output to the file. They're not needed so much in this (only a few write statements) as they are in my other generators, but I got in the habit of using them. refresh() isn't used in my code and can be safely removed - and probably should be - but I left it in in case I ended up with an episode that potentially repeating back into itself.

Code: Select all
<head>

<title>**Your title goes here**</title>

<!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/ -->

<style>
BODY {
    background: #663333;
    color: #ff6666;
} A:link {
   font-weight:normal;
   color: #ffeeee;
   text-decoration: none;
}
A:visited {
   font-weight:normal;
   color: #ffeeee;
   text-decoration:none;
}
a:hover {
   font-weight:bold;
   color: #ffeeee;
   text-decoration:underline;
}
.credits{
   font-size:smaller;
   font-style:italics;
   text-decoration:none;
   text-align:center;
} .names {
   color: #ffccaa;
   font-style: italic;
}
.title {
   color: #ffccaa;
   font-size:larger;
   font-weight:bold;
   font-variant:small-caps;
}
.highlight {
   color: #eebb99;
}
.bighighlight {
   font-size:larger;
   font-weight:bold;
   color: #eebb99;
}
.tablemain {
   border-right: #111111 2px dashed;
   border-left: #111111 2px dashed;
   border-top: #111111 2px dashed;
   border-bottom: #111111 2px dashed;
   padding-left: 5px;
   padding-right: 5px;
   padding-top: 5px;
   padding-bottom: 5px;
   width: 100%;
}
TD {
border-right: #111111 2px dotted;
   border-left: #111111 2px dotted;
   border-top: #111111 2px dotted;
   border-bottom: #111111 2px dotted;
   padding-left: 5px;
   padding-right: 5px;
   padding-top: 5px;
   padding-bottom: 5px;
}
</style>
</head>


Easy stuff, between the title tags place the title of your story. This will show on every episode (the title bar of the browser, and the default name for bookmarks). Unfortunately, browsers tend to output anything between the title tags straight out and I could not get it to dynamicly change to reflect episode number. A PHP solution (preferably using databases) would remedy this.

The meta tag is optional, I used what I needed in my generators to support non-ascii characters (the arrows, music notes, cyrillic/greek/japanese, etc). I ended up not needing any in this story, though the potential was there.

Other meta tags can be added as needed (robots, description, author, etc).

Style can be modified as you see fit. If you remove .credits, .title, or .tablemain, be sure to edit the output section to not use these.

Code: Select all
<body>
<script language="JavaScript">

// main script below

function makeLink(page, text)
// ****************************************************************************************
// ** Receives the destination page and the text for the link, outputs an html-formatted **
// ** string with the link. Gets the link by taking the document's location, trimming    **
// ** off the search string, and appending a new search string.                          **
// ****************************************************************************************
{
 trimUrl="";
 trimUrl += document.location;
 trimUrl = trimUrl.substr(0,trimUrl.indexOf("?"));
 retVal  = "<a href="";
 retVal += trimUrl;
 retVal += "?branch=";
 retVal += page;
 retVal += ";">";
 retVal += text;
 retVal += "</a><br>";
 return retVal;
}

Nothing needs to be changed here unless you are expanding the functionality to incude things like an inventory, health, and so on. Then anything that needs to be passed to the page again needs to be written out in the makeLink function, and the function needs to be ammended to support more information passed to it. makeLink() will be used below.

That <br> is needed, since most episodes will call makeLink() multiple times, building a long string of links and to output it nicely on the screen (one per line), the line break helps. It could be modified to be done as a paragraph for each link, or even ordered or unordered list if you like.

Code: Select all
var title=""; // these 3 need to be cleared each time
var text="";  //                " "
var links=""; //                " "

These three things will be written out after the below switch/case statement is evaluated. Their values will be set in the various cases.

Code: Select all
// ******************************************************************************
// ** HUGE switch case that sets title, text, and links for the output section **
// ** to use. Text and links are html-formatted, title is plain text           **
// ******************************************************************************
switch (branch)
{
 case #:
 {
  title = "";
  text = "****";
  links += makeLink(#,"");
  links += makeLink(#,"");
  break;
 }

 case #:
 {
  title = "";
  text = "****";
  links += makeLink(#,"");
  links += makeLink(#,"");
  break;
 }

Obviously, these will be populated with data, one for each "episode" you have, making for a good many of them, and for a long segment of code.

I recommend that while you are sketching it out and building the structure to the story before actually writing everything, that you populate it with a little information that you can change later.

Example wrote:
Code: Select all
 case 0:
 {
  title = "The Intro";
  text = "**a brief descrption of the scene, establishing characters, there's two options, fight the orc or run away**";
  links += makeLink(1,"It's Clobbering Time!");
  links += makeLink(2,"Brave, Brave Sir Robin");
  break;
 }

 case 1:
 {
  title = "Streetcorner Brawl";
  text = "**Get in a fight with the orc**";
  links += makeLink(3,"Winner Takes All");
  links += makeLink(-999,"Need to plan a lose-the-battle arc");
  break;
 }

 case 2:
 {
  title = "These Boots Were Made For Running";
  text = "**runs away, but can he get away, or is the orc too fast?**";
  links += makeLink(4,"Ah! A place to hide");
  links += makeLink(5,"Too slow, Billy-Jo!");
  links += makeLink(-999,"Need to plan a fast-enough arc");
  break;
 }


Title is plain text though you can html-format it. Recommended that you don't as it has its own style set in the stylesheet. Text and links are both html formatted, meaning you're going to need to use <p>...</p> tags as well as the relevant ones for emphasis or other formatting necessary (displaying images, links, lists, tables (not recommened), etc)

There should always be at least one links += makeLink(#,""); for each episode, leading to the end episode, back to the start, or forward (or laterally) in the story. An episode should generally not have too many choices, however, otherwise it starts to get messy.

Recommend that each paragraph go in its own text = ""; or text += ""; line, for ease of editing.

Example wrote:
Code: Select all
 case 809:
 {
  title = "Denial is the First Step";
  text = "<p>Even with everything that's happened, even considering a <em>talking</em> cat, it's just too much to believe that  you're supposed to have been born a girl. Even with the cat's assurances, you won't let her try to activate your 'powers.'</p>";
  text += "<p>The cat is disappointed, to be sure, but resolves to not give up, and she tags along with you as you head home,  thought of finding the group of girls driven from your mind as you deal with the talking cat - whose name happens to be Kokoro.  She keeps at you for several days, yet you remain steadfast in your refusal to be 'powered-up.'</p>";
  text += "<p>Gradually, Kokoro comes to realize that you are just as stubborn as she is, if not more so, and the pestering becomes  more and more infrequent, almost settling into a routine running gag of sorts. Fortunately, you never find yourself in a position  where you would <em>need</em> the powers of a magical girl, and so your life continues mostly normally from here on out - at least  as normal as it can with a talking cat as a pet.</p>";
  links += makeLink(-13,"The End");
  break;
 }

Remember that quote marks (") denote the start and end of the string. If you need quotation marks in the actual paragraphs or internal html-formatting, be sure to escape them properly (") so that they don't cause errors.


Code: Select all
//*************************
//** special cases below **
//*************************

 case -999: // used when writing and not yet arc'd out the link. shouldn't be encountered in normal reading
 {
  title = "Error: invalid link";
  text = "This section was implemented for incomplete story arcs. If you see this the author made a mistake. Email her and let her  know which branch you were on (back button and look at the url) and which option you chose to get this.";
  links += makeLink(0,"Start Over");
  break;
 }
 case -1:
 {
  title = "Ending";
  text = "**Story ending here, nothing special, encourage to try again for a good ending**";
  links += makeLink(0,"Back to the Start");
  links += makeLink(-3,"Credits");
  break;
 }
 case -2:
 {
  title = "Good Ending!";
  text = "**congratulations and all that**";
  links += makeLink(-3,"Credits");
  links += makeLink(-4,"Reward");
  // probably here are links += for each of the endings in the story as a reward.
  break;
 }
 case -3:
 {
  title = "Credits";
  text = "**Credits go here. Be sure to note source derived from per CC license, and any other credits you care to list, such as dedications, thanks, programs used, and so on**";
  links += makeLink(0,"Back to the Start");
  break;
 }
 case -4:
 {
  title = "Reward";
  text = "**Anything that would be an additional reward, such as extended character bios, omake scenes, and so on go here. This can be extended into further arcs if you like. Linked only from the good ending case (-2)**";
  links += makeLink(0,"Back to the Start");
  break;
 }


 /* Copy-paste the below with obvious changes to create new episodes.
 case #:
 {
  title = "";
  text = "****";
  links += makeLink(#,"");
  links += makeLink(#,"");
  break;
 }
*/

 default: // "random" numbers assigned to pages so if a player tries to guess numbers, they get this error
 {
  title = "Error Page";
  text = "If you are seeing this page, you either followed a link to an episode that does not exists or you attempted to modify  the link to jump to a specific episode that does not exist. Please use either the browser's back button or follow the link below  back to the story's start.";
  links += makeLink(0,"Start Over");
 }
}
// end switch


Numbering, of course, should be changed to fit your own needs and styles. Be sure to ensure that relevant links are also modified, or things can break.

Leave the copy-paste comment in there for future use.

the default case is a catch-all for anything not covered by a proper episode number. It is mandatory, but the phrasing can be changed.

Code: Select all
// ***************************
// ** output section follow **
// ***************************
wr("<table class="tablemain">");

wr("<tr><td>");
wr("<p class="title">");
wr(title);
wr("</p>");
wr("</td></tr>");

wr("<tr><td>");
wr(text);
wr("</td></tr>");

wr("<tr><td>");
wr(links);
wr("</td></tr>");

wr("<tr><td>");
wr("<p class="credits">**story title** &copy;**date** **name** (**email**). (<a  href="http://creativecommons.org/licenses/by-nc-sa/2.5/deed.en">CC - BY NC SA 2.5</a>)</p>");
wr("</td></tr></table>");

</script>

note the use of escaped quotes and mentioned above (")

Nothing really needs to be changed here, other than the obvious replacements on the credits line (and you can customize that) Full credits of course go in the case/episode for them, but these are output on every episode.

Code: Select all
<noscript>
<table class="tablemain">
<tr><td><p class="title">Error!</p></td></tr>
<tr><td><p>Sorry, but **title** requires JavaScript enabled in order to read. Please enable JavaScript or use a scripting-compatable browser.</p></td></tr>
<tr><td><p class="credits">**title** &copy;**date** **name** (**email**). (<a  href="http://creativecommons.org/licenses/by-nc-sa/2.5/deed.en">CC - BY NC SA 2.5</a>)</p></td></tr>
</table>
</noscript>

</body></html>

Anytime you use script for essential information on an html page, you should use a <noscript>...</noscript> set of tags to include standard html, either as a plain version of the page if it uses javascript for fancy functions, a link to a non-scripted version, or a statement saying that the page requires javascript to run.

The credit line here is the same as above, but note that now we're into regular html and not javascript there is no longer any need to escape the quotes.

And that's all there is to it. A little bit of real code that doesn't need much, if any modification and lots and lots and lots and lots and... (well you get the point) lots of episodes formatted properly in the switch cases.

Go for it! ^.^

A way to say "thank you".

PostPosted: Fri Apr 20, 2007 7:48 pm
by Beyond
Oh, ho. Why not XML ourselves?

story.js
Code: Select all
var storyDom;

var invalidBranch;

var titleDiv;
var contentDiv;
var linksDiv;

function initPage(xmlFile) {
   try {
      titleDiv = document.getElementById('title');
      contentDiv = document.getElementById('content');
      linksDiv = document.getElementById('links');
      
      storyDom = loadXMLDoc(xmlFile);
      
      var startBranchId = storyDom.documentElement.getAttribute("start");
      var invalidBranchId = storyDom.documentElement.getAttribute("invalid");
      
      invalidBranch = searchBranch(invalidBranchId);
      
      selectBranch(startBranchId);
   } catch (e) {
      titleDiv.innerHTML = "Error";
      switch (e) {
         case "UnsupportedException":
            contentDiv.innerHTML = "XMLDoc not supported";
            break;
         case "BranchNotFoundException":
            contentDiv.innerHTML = xmlFile + " requires an \"invalid\" branch";
            break;
         default:
            alert(e);
            contentDiv.innerHTML = "<p>This is not right...</p><p>"+e+"</p>";
      }
   }
}

function loadXMLDoc(url) {
   var xmlDoc;
   if (document.implementation && document.implementation.createDocument) {
      xmlDoc = document.implementation.createDocument("","",null);
   } else if (window.ActiveXObject) {
      xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
   } else {
      throw "UnsupportedException";
   }
   xmlDoc.async=false;
   xmlDoc.load(url);
   return xmlDoc;
}

function selectBranch(id) {
   try {
      displayBranch(searchBranch(id));
   } catch (e) {
      if (e == "BranchNotFoundException") {
         displayBranch(invalidBranch);
      } else {
         contentDiv.innerHTML = "<p>This is not right...</p><p>"+e+"</p>";
      }
   }
}

function searchBranch(id) {
   var branch = storyDom.documentElement.getElementsByTagName("branch");
   for (var i = 0; i < branch.length; i++) {
      if (branch[i].getAttribute("id")==id) {
         return branch[i];
      }
   }
   throw "BranchNotFoundException";
}

function displayBranch(node) {
   var title = node.getElementsByTagName("title");
   var titleHTML = "";
   for (var i = 0; i < title.length; i++) {
      titleHTML += "<div>";
      titleHTML += title[i].firstChild.data;
      titleHTML += "</div>";
   }
   titleDiv.innerHTML = titleHTML;
   
   var content = node.getElementsByTagName("content");
   var contentHTML = "";
   for (var i = 0; i < content.length; i++) {
      contentHTML += "<div>";
      contentHTML += content[i].firstChild.data;
      contentHTML += "</div>";
   }
   contentDiv.innerHTML = contentHTML;
   
   var link = node.getElementsByTagName("link");
   var linksHTML = "<ul>";
   for (var i = 0; i < link.length; i++) {
      linksHTML += "<li>";
      linksHTML += "<a href=\"javascript:selectBranch('" + link[i].getAttribute("target") + "')\">";
      linksHTML += link[i].firstChild.data;
      linksHTML += "</a>";
      linksHTML += "</li>";
   }
   linksHTML += "</ul>";
   linksDiv.innerHTML = linksHTML;
}


story.css
Code: Select all
@charset "utf-8";
/* CSS Document */

body {background-color:#633; color:#f66;}
a:link {color:#FEE; font-weight:normal; text-decoration:none;}
a:visited {color:#fee; font-weight:normal; text-decoration:none;}
a:hover {color:#fee; font-weight:bold; text-decoration:underline;}

#main {border:#111 2px dashed; padding:5px;}
#title, #content, #links, #footer {border:#111 2px dotted; margin:2px; padding:5px;}
#title {color:#FCA; font-size:larger; font-weight:bold; font-variant:small-caps;}
#content p {margin:0 0 1em;}
#links ul {margin:0; padding:0; list-style:none;}
#footer {font-size:smaller; font-style:italics; text-align:center; text-decoration:none;}


story.html
Code: Select all
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Story</title>
<link rel="stylesheet" href="story.css" type="text/css">
<script type="text/javascript" src="story.js"></script>
</head>
<body>
<div id="main">
   <div id="title">Loading...</div>
   <div id="content">
      <noscript><p>This story requires JavaScript enabled in order to read. Please enable JavaScript or use a scripting-compatable browser.</p></noscript>
   </div>
   <div id="links"></div>
   <div id="footer">Code &copy;2007.04.20 Beyond at <a href="http://www.magicalsailorfuku.net">MSF</a> (pllamosas |at| gmail |dot| com).<br />
      <a href="http://creativecommons.org/licenses/by-nc-sa/2.5/deed.en">CC - BY NC SA 2.5</a>
   </div>
</div>
<script>
initPage("story.xml");
</script>
</body>
</html>

Save files as UTF8 not ISO blah blah blah.

PostPosted: Fri Apr 20, 2007 7:49 pm
by Beyond
story.xml

Code: Select all
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root
[
  <!ELEMENT root (branch*)>
  <!ATTLIST root start CDATA "start">
  <!ATTLIST root invalid CDATA "invalid">
  <!ELEMENT branch (title, content, link*)>
  <!ATTLIST branch id CDATA #REQUIRED>
  <!ELEMENT title (#PCDATA)>
  <!ELEMENT content (#PCDATA)>
  <!ELEMENT link (#PCDATA)>
  <!ATTLIST link target CDATA #REQUIRED>
]>
<root>
   <branch id="invalid">
      <title>Error Page</title>
      <content>It goes here if the link does not exist.</content>
      <link target="start">Start Over</link>
   </branch>
        <branch id="start">
      <title>Story start page</title>
      <content>Here goes the content of the story</content>
      <link target="a_target">Start Over</link>
      <link target="another_target">Start Over</link>
   </branch>
   <branch id="a_target">
      <title>Story start page</title>
      <content><![CDATA[
<p>HTML? In my story?</p>
<p>It's more likely than you think.</p>
                ]]></content>
      <link target="another_target">Start Over</link>
   </branch>
   <branch id="another_target">
      <title>The End</title>
      <content>Fin</content>
   </branch>
</root>

PostPosted: Fri Apr 20, 2007 8:19 pm
by Beyond
Okay, here is the deal.

Javascript is hard to edit for someone who doesn't know to code. So I though it may be better to have it in XML since there are plenty XML editors arround. You can even use a XML flow editor to create your story. (I could even code a XML editor if people are interested enough to make stories...).

You only need to edit the story.xml file following the model to create more branches in your story
just like this:
Code: Select all
<branch id="the_id_of_the_branch">
      <title>Branch Title.</title>
      <content>The content of your branch.</content>
      <link target="a_link_to_another_branch_id">A Link</link>
</branch>


You only have to create more branches and more links to those branches in order to create your story. The code two posts above will take care of the format.

Here is an example using Love-Love 4/5 story: (Thanks Nikkou for the hosting)
http://www.angelfire.com/anime/next/Beyond/story.html

°3° Easy enough. It's your turn to create.