Changing Room Programming Library (libcirce)

Stories~! Art~! CREATIVITY~! Anything the creative juices in your brain might happen to create is welcome here~!

Moderator: Raleigh

Changing Room Programming Library (libcirce)

Postby Loremistress Eirien » Thu Dec 27, 2007 5:30 pm

Okay, so it's not exactly the nicest looking page for the time being, but the new and old changing rooms are slowly starting to get updated and moved to http://www.magicalsailorfuku.net/changingrooms/. Autumn, you might want to take a look at the examples (especially the original changing room, since it's relatively simple) to see how the little Javascript library I wrote up (libcirce) works to ease making these things.

(Technological ramblings ahoy!)

Basically, I split up content from style and code, so that each page is now split into three files. For example, there's

http://www.magicalsailorfuku.net/changi ... gRoom.html
http://www.magicalsailorfuku.net/changi ... ngRoom.css
http://www.magicalsailorfuku.net/changi ... ingRoom.js

The HTML file is more or less a template. As you can see from the code, about all it is is the HTML framework that the data is pushed into.

A bunch of <span> elements are used to serve as placeholders for generated data. In particular, anything with a class name that begins with 'var' corresponds to a placeholder for a particular variable. For example the class name 'varFirstName' is used as a placeholder for the firstName variable. You can reuse the class name and have the same value placed in multiple locations, such as if you want to have several sentences that have the victim's name. You can also style based on the class name.

The CSS file is simple. The stylesheet information is just spun off into a different file.

The JavaScript file is where the action's at, and ties in with the order of scripts in the HTML header.

The main library that does the magic we need is lib/libcirce.js. You don't need to know the details, other than that it provides all the functionality we need. It depends on lib/prototype.js (the Prototype library), and other scripts can be used as well as needed. Thus, prototype must be included first, followed by libcirce, and lastly, the actual script file.

The script file itself is actually pretty easy to write. You just need to create an instance of the ChangingRoom class, which you will use for customizing the specific room.

The class provides a handy helper function, addRandomVariable() that takes, as its first argument, the name of the variable you're adding (e.g. 'firstName' or 'FirstName' will correspond to 'varFirstName' in the HTML file), and the remaining arguments are the possible values that variable may take. The arguments are saved as an array to (instance).randVars.(property), (e.g. this.randVars.FirstName). This helper function also adds a convenient getter function to the instance, 'get(variablename)'. By this I mean that a variable with the name 'firstName' or 'FirstName' will have a function created, 'getFirstName()', which will fetch a random value from the possible first name values.

The values you specify are evaluated with 'eval()', so it IS possible to nest randomization values, as in vg0.js's gen1 variable (You will need to make sure that quotes are balanced. You don't have to add quotes to each side of a possible value. It's already 'quoted' before a random value is plucked out, so "this\" + that + \"okay" will be evaluated as eval("\"this\" + that + \"okay\"")).

The default 'getter' functions can be called easily by way of '(classname).(functionname)' so that you could easily call this.getFirstName() in an instance method, or even changingRoom.getFirstName() from outside the class itself. You can also overwrite the default getters and make new ones if you have some dependencies or special logic you want to implement, as both changing rooms have.

When it comes down to actually updating the page however, you need to take a look at the bottom of the Javascript files. In particular, note the presence of 'function refresh()'. This function (or any other function name really) serves as the 'refresher' function for the particular page. The general principle of the 'refresh' function is that it a) sets up the refresh links so that the page need not be reloaded, and b) it actually updates the page. How it does so is with the simple two-part dance:

you = (changing-room-instance).doTransformation();
you.updateDocument();

The former line generates a victim by calling all functions that start with the string 'get', and assigning these values to appropriate properties. For example, getFirstName() would be called, and the value created would be set to you.firstName. All other 'getters' are called in this fashion until exhausted, after which the 'transformed victim' is returned.

It is important to note that all functions that begin with 'get' should take no arguments. Why? These functions are called automatically when updating the page, and the library is not smart enough to realize that arguments must be passed. If you absolutely must pass an argument to a function, you may want to create a wrapper-getter, like getMoves() in vg0.js that wraps the arguments needed for processing and creating moves.

updateDocument, on the other hand, takes all string and array valued properties of the object and updates the document. For example, it would note the property firstName, and then update ALL elements of class 'varFirstName' in the document to have innerHTML equal to the value in property firstName. Some special handling is done for arrays. An array, like move from vg0's 'getMove()' function, is translated to a series of classes 'varMove_0', 'varMove_1', and so on. These are updated similarly. The only difference is the underscore syntax to represent array values.

A couple of things to be careful of:

It is possible to create dependencies (e.g. how zodiac sign depends on the birthday) through two steps:

1. The getter function should be added to an array, (changing-room-instance).dependents. Be careful though, as the library doesn't resolve dependencies. All it does is withhold processing of the functions listed in the array until after all other getters are processed. It then processes dependents in order. For example, 'getWesternZodiac' depends on 'getBirthTime' to have been executed before it can execute. It, hence, goes into the dependents array, so that it is postponed until all others have been processed.
2. Before the victim is complete, you can access values generated through 'this.curVictim.(propertyname)', if it's an instance method, or '(instance).curVictim.(propertyname)' outside the class. For example, getWesternZodiac gets the generated birth time by getting it from 'this.curVictim.birthTime'. You can then manipulate it normally, but don't forget to add it as a dependent.

Also, be careful of capitalization as much of the code is case-sensitive:

- Things that have the first letter of the variable, capitalized (e.g. 'firstName' -> 'FirstName'): getters (getFirstName), variable holders (varFirstName), and the array of random values (this.randVars.FirstName).
- Things that have the first letter of the variable, downcased (e.g. 'FirstName' -> 'firstName'): properties of the victim (you.firstName, this.curVictim.firstName).

Finally, the last and perhaps most important question is how to actually set up the changing room to work:

Prototype makes this easy: 'Event.observe(window, 'load', refresh);'. This just sets up the refresh function to run at page-load.
User avatar
Loremistress Eirien
Sensei
Sensei
 
Posts: 1483
Joined: Fri Jan 09, 2004 10:21 pm
Location: In my study calculating something. Why do you ask?

Re: New MSF Site!

Postby Moonlit Naiad » Thu Dec 27, 2007 7:23 pm

Alrighty,

I'll have to give a looksee this weekend (I have a four-day, again) to see how everything lines up. Ideally, my preference is to have .css, .js, and the template files separate, and that's how I used to do them, until it came time to start emailing them to others. Multiple files tends to increase opportunities for errors -- as I learned when we did the xmas one a few years back. Which is one of the reasons I tend to avoid images with the file -- the other being limited graphical ability and a desire to not 'rip off' the work of others. So splitting them again works fine with me..

Unfortunately, given the rapidly approaching deadline to ensure the next one remains 'topical' I may not be able to do the January one in the new format initially, and it may have to be 'ported' to work in the new style.

Quick question: How badly will it mess up things if I'm pulling info from the query string in order to determine how the page loads? For example:
psuedocode wrote:
Code: Select all
switch(mode){
  case START: // code here to display intro/selection screen
    break;
  case GO:
    switch(type){
      case BLUE: // code here to display gen for male results
        break;
      case PINK: // code here to display gen for female results
        break;} // end inner switch
     break;
  case ABOUT: // code here to display credits/about screen
     break;
  default: // code here to display error screen
    break;
 } // end switch
where the querystring could be as one of the following
?screenID=START
?screenID=GO&type=PINK
?screenID=GO&type=BLUE
?screenID=ABOUT
?screenID=ERROR
Because each would require a different template, at the very least, and pink and blue would obviously pull from some same arrays and some different arrays. eye and hair colour the same, for instance, but hair and clothing style different...

And... perhaps some of this convo can be split off, since it's sorta not qualifying so much as news at the nonce?
*POOF* Problem Solved.

星空ARRANGE
User avatar
Moonlit Naiad
Retired Moderator
Retired Moderator
 
Posts: 496
Joined: Mon Jan 12, 2004 3:14 pm
Location: Elsewhere

Re: New MSF Site!

Postby Loremistress Eirien » Thu Dec 27, 2007 11:25 pm

Moonlit Naiad wrote:Quick question: How badly will it mess up things if I'm pulling info from the query string in order to determine how the page loads? For example:
psuedocode wrote:
Code: Select all
switch(mode){
  case START: // code here to display intro/selection screen
    break;
  case GO:
    switch(type){
      case BLUE: // code here to display gen for male results
        break;
      case PINK: // code here to display gen for female results
        break;} // end inner switch
     break;
  case ABOUT: // code here to display credits/about screen
     break;
  default: // code here to display error screen
    break;
 } // end switch
where the querystring could be as one of the following
?screenID=START
?screenID=GO&type=PINK
?screenID=GO&type=BLUE
?screenID=ABOUT
?screenID=ERROR
Because each would require a different template, at the very least, and pink and blue would obviously pull from some same arrays and some different arrays. eye and hair colour the same, for instance, but hair and clothing style different...

And... perhaps some of this convo can be split off, since it's sorta not qualifying so much as news at the nonce?


Point, but as a casual note, it ought to be possible. You'd need some out-of-band stuff (e.g. in Refresh) to handle hiding the irrelevant templates with a 'display: none', but it ought to work fine. You could also customize the refresh function. Basically, the only things that are 'hard-coded' are that the generators are called without variables (so you either need to pass around by making it dependent on another generator or store some extra data in the class), all 'get' functions set the corresponding properties on the victim, and 'updateDocument' updates the corresponding template spaces. I know there's already extra stuff generated that doesn't get set even now, so it's not a big deal. So... something like this...

Code: Select all
HTML:
<div id="START">
<!-- start template -->
</div>
<div id="BLUE">
<!-- blue template -->
</div>
<div id="PINK">
<!-- pink template -->
</div>
<!-- ... And so on -->

JavaScript:
function Refresh() {
  document.getElementById('START').style.display = 'none';
  document.getElementById('BLUE').style.display = 'none';
  document.getElementById('PINK').style.display = 'none';
  // and so on
  switch(mode) {
  case START:
    document.getElementById('START').style.display = 'block';
    // do start stuff
    break;
  // and so on
  }
  you = changingRoom.doTransformation();
  you.updateDocument();
}


So essentially, the entire template is populated at load time, and then each 'page' is hidden or shown as needed. You could even further lessen the number of pings to the server by dynamically showing and hiding each block with a Javascript onClick, since there's no external data needed for the population of the template.
User avatar
Loremistress Eirien
Sensei
Sensei
 
Posts: 1483
Joined: Fri Jan 09, 2004 10:21 pm
Location: In my study calculating something. Why do you ask?

Re: Changing Room Programming Library (libcirce)

Postby Loremistress Eirien » Fri Dec 28, 2007 4:31 am

Fixed the UTF-8 bug by the way. Seems Mozilla likes to ignore http-equiv tags when getting stuff via HTTP. :)
User avatar
Loremistress Eirien
Sensei
Sensei
 
Posts: 1483
Joined: Fri Jan 09, 2004 10:21 pm
Location: In my study calculating something. Why do you ask?

Re: Changing Room Programming Library (libcirce)

Postby Cutey Kerina » Fri Dec 28, 2007 4:40 am

Btw, if you want anyone to contribute additional scripts for the random option, I'd love to offer as much as I can to provide as thoroughly randomized and diverse an experience as possible

By that I mean potential results...I no know code X_X
Image
http://photobucket.com/albums/v340/Majorkerina/ Kashimashi here in "Zappy Manga"
"I'm begging you. Establish context before you start talking." - Kyon
User avatar
Cutey Kerina
Excited MSFer
Excited MSFer
 
Posts: 1333
Joined: Thu Jul 15, 2004 10:57 pm
Location: West Coast

Re: Changing Room Programming Library (libcirce)

Postby Moonlit Naiad » Mon Dec 31, 2007 2:03 am

I skimmed through them, E, but I'm going to need more time than I have readily available right now to experiment and use them, so this January thing is going to have to be done 'old-style' and then ported to use libcirce later, which will be a good learning experience for me :p Stick to the tried and true, mostly, for rushing against deadlines. ^^;;

But... I think part of how I am doing it will make it ... hard to 'port', since this particular one will need to generate a few things of 'victim' traits that will not be outwardly displayed, but used in generating blurbs or selecting options.

Basically, I'm putting much of the generation of the whole blurbs into functions that generate a formatted paragraph/blob of text.

I haven't finalized the generating sections yet, so I haven't decided what all needs to be available to more than one function (age, name prolly), so I won't include the object declaration, but here's what I'm using for outputting on the mode=GO section
Code: Select all
    wr("<table class=\"tablemain\">");
    wr("<tr><td>");
    wr(you.storyBlurb);
    wr("</td></tr>");
    wr("<tr><td>");
    wr(you.descriptionBlurb)
    wr("</td></tr>");
    wr("<tr><td>");
    wr(you.diaryBlurb);
    wr("</td></tr>");
    wr("<tr><td>");
    wr("<p align=\"center\">.. o O (<a href=\"javascript:refresh()\">One More Time?</a>) O o ..</p>");
    wr(makePinkOrBlueLink());
    //wr("</td></tr>");
    //wr("<tr><td>");
    wr(makeCreditsLine());
    wr("<tr><td>");
    wr("</td></tr></table>");


So that would only be a couple things to write in the template section of the newstyle, once converted, but I will have more properties on the you object than just the three blurbs, which if I read it correctly will break the script needed to take the properties and find the relevant section of the template to rewrite.

Would the best way to redo it for the new style be to have global-scope variables to store the information needed in multiple places and just leave the stuff for template output on the you/victim object, or...?

--

And Kerina, I won't be able to take much input on this one, but I may solicit input for the next one I work on (Feb, so valentine's theme).

Roughly, these so far are working roughly like (complex) madlibs, so one thing you can do, if you like, is create a script (not code meaning, but writing meaning) of options and styles, and see how it develops from there.

Something like
Code: Select all
hair colour options: red, green, yellow, blue, orange, pink
eye colour options: red, green, yellow, blue, orange, pink
clothing options: seifuku, OL, waitress uniform, maid uniform, tuxedo
change options: magic, nanites, zappy-touch, mysterious plague, merging of worlds
etc...

Story Option 1:
You are walking home from <location option> when you pass a <person option> who <action option> at you. <sudden adverb option> you feel <sensation option>. Things fade to <color option> for a <time option> and then ...

...

Story Option n:
<Natural disaster option> strikes! <Dramatic/chaotic  emotion noun option> reins supreme. Suddenly you see <invader option> descend from a <flying transport option> in the <sky word option>. They <grab take word option> you and return to their <same flying transport> where they <unpleasant action option> you. And then...

--

Description (girl) You find yourself transformed into a <adjective1>, <adjective2> <girl word option>, with <hair colour option> hair done in a <hair description option>;  <eye adjective> <eye colour> eyes, and a <smile adjective> <smile word option>. You are wearing <long clothing description generated in a different option>.

Description (guy) You find yourself transformed into a <adjective1>, <adjective2> <guy word option>, with <hair adjectie optiom> <hair colour option> hair;  <eye adjective> <eye colour> eyes, and a <expression adjective> <expression option>. Yoou are wearing <long clothing description generated in a different option>.

Epilog 1 <etc>
Epilog 2 <etc>
Epilog 3 <etc?

---

<Random Story> <Random Desc> <Random Epilog>


And now back to work... after I check how the pie is cooking. ^^
*POOF* Problem Solved.

星空ARRANGE
User avatar
Moonlit Naiad
Retired Moderator
Retired Moderator
 
Posts: 496
Joined: Mon Jan 12, 2004 3:14 pm
Location: Elsewhere

Re: Changing Room Programming Library (libcirce)

Postby Loremistress Eirien » Mon Dec 31, 2007 3:39 am

Do you mean there will be 'invisible' properties of the victim that alter other aspects? The script will only automatically execute the 'get' functions, but to get what you want, you'd just put the getters that depend on other getters in the dependents array. I'm not exactly sure how but, here are what I imagine the scenarios you could have are:

1. A blurb depends on some variable that is used only once, in that blurb. If so, the blurb can just have the following code added at the right place.

Code: Select all
" + this.getVariable() + "


(Including quotes) This is because what the script does upon generating the random variable is attach quotes to either side of the randomly selected string and run it through eval() to get things like embedded strings to actually embed themselves. Your Ka-Zap! script has been ported to do this.

2. A blurb depends on some variable that is reused, either in the same blurb or in another (e.g. name). If so, we need to add an extra step on top of changing this.getVariable() to this.curVictim.variable (to represent the staticly selected variable): the getter function(s) needs to be added to the list of dependents. For example:

Suppose you have two blurbs variables, storyBlurb and descriptionBlurb. Both depend on a static variable, name, and need it to be the same. The storyBlurb strings would include " + this.curVictim.name + " (including quotes) as would descriptionBlurb. This goes the extra mile to ensure that a single variable setting is used in both locations. In order to assure that getName() is processed first (in order to ensure that this.curVictim.name is set), this.dependents now has to include "getStoryBlurb" and "getDescriptionBlurb" in it, so that they can both be processed AFTER the name has been.

3. A blurb depends on a variable, but this variable is not directly embedded in the string, but used in something like an if statement. If so, it's more or less like 2 (i.e. we need to add the function to this.dependents, and we can get the value from this.curVictim.variable).

In all three cases, the VALUE of a random variable is set to a property ONLY if there is a corresponding function that starts with 'get'. Furthermore, the script doesn't mind if no template-holder exists for a variable. If, in the above examples, varName is never set for ANY template variable, then it's perfectly fine. The 'name' property isn't outright hidden on the JavaScript object, but it's not like it will be automatically inserted anywhere, unless you have something with the class "varName".

In the long run, I'd like to clean this up a bit in one of two ways:

1. Dependency resolution (so that we don't have to manually create the dependent array)
2. Let the getter function only run ONCE while we are doing a transformation unless an argument is passed to ignore this check. That way, we can use the getter function indiscriminately, and always get the same answer we got the first time (right now, getName() would return a different value the second time it was called).
User avatar
Loremistress Eirien
Sensei
Sensei
 
Posts: 1483
Joined: Fri Jan 09, 2004 10:21 pm
Location: In my study calculating something. Why do you ask?

Re: Changing Room Programming Library (libcirce)

Postby Loremistress Eirien » Mon Dec 31, 2007 2:10 pm

Made a couple of fixes to libcirce for easing coding. In particular, the helper function addGetter() has been added to the ChangingRoom class. Like addRandomVariable(), addGetter() takes, as a first argument, the name of the variable that will eventually be set in the final class.

The second argument should be a function that, again, executes to pick the value, assuming that no caching has taken place. addGetter upcases the first letter of the variable name (so addGetter("that", ...) will create a function getThat(), property you.That, and so on), and furthermore, adds a layer of caching to all of the getters, including those made with addRandomVariable(). Now, you may call ChangingRoom.getName() and you will consistently get the same random name every time it is called. Likewise, you may call ChangingRoom.getName(true) for the old behavior of getting a new random name, though the name returned that way is not cached.

Thus, in Ka-Zap!, we can have the following code for makeMoveName(), getMoves(), and getMoveNames():

Code: Select all
fighter.makeMoveName = function(attack) {
  var attackName = "";
  var attackStyle = this.determineAttackType(attack);
  var useOwnName = Math.randInt(0, 20);
  var chanceOfGeneric = 10; // 2 out of value
 
  if (useOwnName == 0) attackName += this.getFirstName() + "'s";
  else if (useOwnName == 1) attackName += this.getLastName();
  else attackName += this.getAttackAdjective(true);
  attackName += " ";
  if (attackStyle != "S" && Math.randInt(1, chanceOfGeneric) <= 2)
    attackName += this.getGenericWord(true);
  else
    switch(attackStyle) {
    case "P": attackName += this.getPunchWord(true); break;
    case "K": attackName += this.getKickWord(true); break;
    case "G": attackName += this.getGuardWord(true); break;
    case "T": attackName += this.getThrowWord(true); break;
    case "C": attackName += this.getComboWord(true); break;
    case "S": //no generic words for stance
      attackName += this.getStanceWord(true);
      break;
    }
 
  return attackName;
};

fighter.addGetter("Moves", function() {
    return new Array(fighter.makeBasicMove(Math.randInt(2, 4)),
           fighter.makeBasicMove(Math.randInt(3, 5)),
           fighter.makeThrow(Math.randInt(3, 5)),
           fighter.makeMajorMove(Math.randInt(6, 8)));
  });
fighter.addGetter("MoveNames", function() {
    return new Array(fighter.makeMoveName(fighter.getMoves()[0]),
           fighter.makeMoveName(fighter.getMoves()[1]),
           fighter.makeMoveName(fighter.getMoves()[2]),
           fighter.makeMoveName(fighter.getMoves()[3]));
  });


Note several aspects of the new code:

1. makeMoveName() calls this.getFirstName() without argument, but this.getAttackAdjective(true). This ensures that it does not create (or get) a new first name every time it is called, but DOES get a new attack adjective every time it is called.
2. getMoves() is added through the addGetter() function, which adds the function that generates moves. in the moves themselves, this.getMoveFollowsBlah(true) is used to ensure that the chain of moves is random rather than the same every time.
3. Note that we can't used the this keyword in addGetter().
4. getMoveNames() again does NOT use an argument, so that the list of moves is not randomized every time it is called (in this way, the only way to re-randomize the move chains is by calling getMoves(true) directly, followed by getMoveNames(true) again as well. getMoveNames(true) on its own only randomizes the names, though I suppose you could make the first fighter.getMoves()[0] into fighter.getMoves(true)[0], leaving the other array values alone, to randomize the movesets at the same time.=

Hopefully this resolves some confusion issues as well, as now, a capitalized first letter is used in all cases where the variable is used, and there is no longer a need to maintain dependencies, as the caching functionality of the getters ensures that the age, firstName, etc. is SET once, and only once, whether that be in a nested call first or not.
User avatar
Loremistress Eirien
Sensei
Sensei
 
Posts: 1483
Joined: Fri Jan 09, 2004 10:21 pm
Location: In my study calculating something. Why do you ask?


Return to Creative Corner

Who is online

Users browsing this forum: No registered users and 4 guests