20110215

More Evento Ordering Woes

I'll let you in on a little secret. I've been working on combat lately. I guess that's not a really great secret, but whatever. I have a roadmap of several features that need to be implemented to have the basic breadth of it working, which will involve simple fights against nipics, and a bit of AI that will make the player work a bit during one of those fights.

One of the most basic features of this combat is that a nipic, if attacked, should retaliate. Seems simple, right? Actually, making this work and look right took me down a rabbit hole of problems with my evento system. It seems quite a while ago, I solved one evento problem, but I recently found that that wasn't the whole story.

No death

The first problem occurred when I attacked a mob enough times that it died. Ideally this would be accompanied by a message about his untimely death, but I found that this was being swallowed somehow, and not shown. The problem turned out to be that upon losing enough HP so as to render him dead, the mob would try to send the message after tearing down his invocation queue, manager, etc.--that is, the machinery required to send that evento out. To fix this I had to make it so that non-IC invocations are allowed to run just before tearing down everything. OOC invocations, by definition not really able to affect the game world, are permitted upon death.

The way this was actually implemented is a bit clever. Now, when you call Nipic#die(), we just start the dying process. First we set our state to "dead." Then we flush all IC invocations from the queue of pending actions (known as the invocation queue). Then we send the DeathEvento. Setting our state to "dead" as early as possible ensures that any actions we try to do will not be permitted (due to being, you know, dead). Flushing IC invocations ensures that nothing the nipic already had waiting will occur.

Then we simply catch the DeathEvento in the usual course of handling messages and finish tearing down at that point. And of course, we since we stop processing any more invocations at death, any subsequent actions will also not be reached.

An early grave

The only problem with this turned out to be that you'd see things in the reverse order, much like I'd solved before. You'd hit the mob, but the death message would be displayed immediately preceding the message that you'd hit him.

The problem--which will turn out to be something of a theme--had to do with inline handling. The code for hitting someone looked something like this:

def doHit(target)
    target.loseHP(10)
    sendEvento(HitEvento.new())
end

def loseHP(amt)
    self.hp = hp() - amt
    if hp() < 0
        die()
    end
end

def die()
    sendEvento(DeathEvento.new())
end

So there are two eventos that get sent here. Because the DeathEvento is being sent at the time the loseHP call is made, that effectively puts it before the HitEvento sent by doHit.

So let's switch the order in doHit. That should fix it, right?

def doHit(target)
    sendEvento(HitEvento.new())
    target.loseHP(10)
end

Well, I didn't show you the whole picture. Remember that a nipic will retaliate when hit.

# think "onHit". aee stands for "after experience evento"
def aeeHit(evento)
    if (evento.kind_of?(HitEvento) && evento.target == self)
        doHit(evento.source)
    end
end

It is useful to see the stack at this doHit call. Note that the stack is growing upwards.

source.die()
source.loseHP()
target.doHit(source)
target.aeeHit()
target.receiveEvento(HitEvento)
source.sendEvento(HitEvento)
source.doHit(target)

What's wrong with this picture? What's wrong is that the target was effectively able to catch the HitEvento and retaliate, killing the source when in fact he himself should have been killed by the hit. So both orderings don't work properly. What we want is this order:

  1. source sends HitEvento
  2. everyone in the room sees HitEvento
  3. target loses HP, potentially dying
  4. target sends DeathEvento. everyone sees it

The way I tackled this is by not doing the loseHP call in doHit but rather in the aeeHit handler. At this point, I'd hope that everyone has seen the HitEvento, so any replies will show up correctly after it.

Replying In-line

That last sentence from the previous section? Totally not true. It was an assumption I had made, but which my code wasn't sticking to. I'd tried to fix it before, but obviously not well enough. The problem stems (here's the theme again) from sending eventos in-line with receiving them. When I say "in-line" I mean later/lower in the stack. For instance, before I fixed up the code, here's a stack you might see. I'll just embellish the stack from before.

room.forwardEvento(each occupant)
room.receiveEvento(HitEvento)
source.sendEvento(HitEvento)
source.die()
source.loseHP()
target.doHit(source)
target.aeeHit()
target.receiveEvento(HitEvento)
room.forwardEvento(each occupant)
room.receiveEvento(HitEvento)
source.sendEvento(HitEvento)
source.doHit(target)

The interesting parts are in blue. When you send an evento to your current room, it gets sent, one at a time, to each occupant. This means that higher up on the stack when the room hasn't delivered the HitEvento to the second half of the room, it's now already delivering a later evento, the DeathEvento to everyone in the room. Half the people will see it in the correct order; half will see it in the reverse order.

With such a flaw, I kind of need to justify reentrant code of this sort. The problem is, I can't really. It seemed like a good idea to deliver eventos as quickly as possible, but maybe it will cause (more) serious complications later. I'm not sure. It's a "known unknown".

How would/did I fix this? By deferring eventos until the next pulse. Either unconditionally (not what I did) or conditionally (what I ended up doing), I can put an action into either the sender or the receiver's invocation queue to deliver the evento on the next pulse. That eliminates the reentrancy and therefore the incorrect ordering.

I mentioned that I do this deferring conditionally. The conditions are based on states that a mob can be in:

  • idle, and receiving an evento
  • idle, and sending an evento
  • in the middle of receiving an evento, and want to send an evento
  • in the middle of sending an evento, and someone else is sending you one
  • in the middle of sending an evento, and need to send another one
  • in the middle of receiving an evento, and receiving another one

Actually, not all of these are possible. I'll explain each in turn.

Sending while idle, receiving while idle

These are the roots of whatever problems this system has. If a mob sends an evento while it is idle, and the receiver of the evento is likewise idle, then the evento is delivered immediately right on that stack.

Sending while already receiving

This is probably the next most common case. When a mob receives an evento and wants to respond to it, it does a sendEvento. Since it's already receiving an evento, if it responded immediately, it would run into the problems I discussed earlier, so it defers the sendEvento call until the next pulse.

Receiving an evento while someone else sends you one

To be honest, I'm not too sure this is actually possible, so I put an assert in the code to let me know if it happens.

Sending an evento while already sending one

This situation seems unlikely, but is possible due to an optimization I allow. It's very common for a mob to send an evento to the room that it's in, which means it will end up being sent to all occupants, including back to itself. With respect to ordering, there's no need to defer the delivery; intuitively the second evento has to be occurring after the first evento it's acting in response to.

So this arises when an evento sent by some mob is handled and acted on by the mob itself.

Receiving an evento while already receiving one

This is another one I'm not sure is possible, so there is an assert for it.

Slowness

All this deferring is making me slow, or so I felt (without any strong evidence to back it up). So I made some modifications to the invocation queue to make these evento changes less noticeable.

The first change was allowing multiple invocations to run on a single pulse. Previously it only allowed a single invocation to run, but now it will run invocations until the queue is empty or until there is some delay. The SendEventoInvocation and ReceiveEventoInvocation invocations both have a delay of 0 pulses, so they will be run without really getting in the way of any other eventos.

The second change I made was a refinement of the first change. It allows certain invocations to run even if the mob is currently busy (*gasp*). As you can probably guess, these invocations tend to fall into the category of evento delivery invocations, but I wrote the code so that it could expand or change without too much reworking.

while (invocation = peekNextInvocation()) && canRunInvocationNow?(invocation)
    dequeueNextInvocation()
    # ...
    runInvocation(invocation)
end

def canRunInvocationNow?(invocation)
    return (!inDelayState?()) || canRunInvocationWhileBusy?(invocation)
end # function canRunInvocationNow?

What I like about this code is that the functions are phrased like the logical question to ask at the given time. When checking if the loop to process invocations can continue and run the current one, we ask, "can I run this invocation now?" And that in turn asks its constituent questions. Only at the deepest level do we query the actual state of the object.

And with that, the state of eventing is much more reasonable now. I think it may be in a state where I can push forward towards the rest of combat. I feel like the basics are in place.

20110211

cat pats

My cat is a weirdo. Sometimes when I walk down the hall, I'll reach down and pet her as I go by.  Sometimes as my leg passes by her, she then swipes at it with her paw, maybe hopping a bit.

at first I thought she was just being a little weirdo, because she certainly is that. then I wondered: what if she's reciprocating? wouldn't that be something?

20110203

ditto

You want to know what nerds talk about at lunch? we dissect things. yesterday someone brought up the usage of the word "ditto", and we had a heated discussion about when it can and when it can't be used.

Our conclusion (or rather my conclusion, I guess) is that "ditto" is only compatible with sentences with a 1st person subject, and it modifies the subject, transplanting the speaker into the position of the speaker of the original sentence.

So I can say, "I think people are dumb", and an appropriate response is "ditto", because that person is stepping in to take my place in the sentence.

On the other hand, if I say, "people are dumb", someone can't say "ditto" to that, because there's no 1st person to step into. This construction just doesn't work.

20110202

random haps 2/2/2011

waddap. haven't been doing the regular blogging thing much. most of what I've written lately is on my destructoid blog instead. I find that, not surprisingly, more people read it, so if I have something coherent to say about games, that's the natural choice.

my wife read a thing somewhere about a dude who stopped bathing with soap, and how it didn't really affect them. I was pretty skeptical at first, since if I don't use soap when I bathe, I get fairly greasy, but I decided to give it a try anyway. To be clear, I still shower once a day, and I still scrub myself down. I just don't use soap for that.

Week 1: felt like a grease ball before, during, and after every shower. It was a bit gross, but not as gross as you might think. I also didn't smell bad or anything, so at least there's that.

Week 2: not much different than week 1.

Week 3: most of my body is no longer that oily before, during, or after the shower. notable exceptions are head, face, ears, neckbeard, lower back, and outsides of my arms near my shoulders (what the...?).

Week 4: all exceptions from before are still oily, but I also mega need a haircut, so maybe that will help with the head at least.

Surprisingly (though not if you think about it a bit), my showers actually take longer now than they used to, because I have to vigorously scrub myself to feel clean, not just wipe all over with soap. That is, I'm now manually doing the work that soap was doing before.

 

Games I finished recently:

  • alan wake DLCs
  • Digital: A Love Story
  • Enslaved (not as good as I hoped, but not bad)

now playing:

  • red dead redemption
  • dead space

20110201

Intro CS Lesson 2: Functions and Algorithmic Thinking

In the previous lesson, I showed you about what functions are and what they do. I also showed you about variables and a bit about how to use them, but for this lesson I'm going to put them aside and spend some more time on functions. I told you what functions are, but not much about why you would use them.

Before we get to the actual programming part, I want to talk about algorithmic thinking, or thinking in a way that can be translated into a program. As I mentioned before, computers are kind of stupid. They're good at doing what you tell them, but you have to be really clear and know exactly what to instruct them to do. Writing programs is a lot about learning this clarity and learning how to express yourself.

Algorithms, what's up with them?

I'm using the term "algorithm," so I should define it. An algorithm is just a list of steps that solve a problem or accomplish some goal. These "steps" are a pretty abstract concept. They can be as vague or as specific as you'd like when you think up the algorithm, but by the time they are translated into a program, they must be specific.

Let's look at a sample algorithm. The problem we want to solve is tidying up a house. What does that entail?

  1. Go into the living room.
  2. Pick up trash lying around.
  3. Put trash in the garbage.
  4. Pick up things off of the floor.
  5. Put things back where they belong.
  6. Plug in the vacuum cleaner.
  7. Vacuum the floor.
  8. Unplug the vacuum cleaner.
  9. Dust the furniture.
  10. Move into the dining room.
  11. Pick up trash lying around.
  12. Put trash in the garbage.
  13. Pick up things off of the floor.
  14. Put things back where they belong.
  15. Plug in the vacuum cleaner.
  16. Vacuum the floor.
  17. Unplug the vacuum cleaner.
  18. Dust the furniture.
  19. Wipe down the dining table.
  20. Move into the kitchen.
  21. ...

This program has several steps. I've described the house cleaning process in pretty good detail (ignore for now whatever house cleaning tasks I forgot to do). But there are problems with this algorithm. First of all, if you have a house with 10 rooms in it, think of how many individual tiny steps are needed in total. The program overall would be very long. Most programs that do anything useful are very long, and one way to manage the problem of length is to break up the problem into pieces and tackle them individually.

If I were to break this program up into a few pieces, here is what I might come up with:

  1. Cleaning the living room

    1. Go into the living room.
    2. Pick up trash lying around.
    3. Put trash in the garbage.
    4. Pick up things off of the floor.
    5. Put things back where they belong.
    6. Plug in the vacuum cleaner.
    7. Vacuum the floor.
    8. Unplug the vacuum cleaner.
    9. Dust the furniture.
  2. Cleaning the dining room

    1. Move into the dining room.
    2. Pick up trash lying around.
    3. Put trash in the garbage.
    4. Pick up things off of the floor.
    5. Put things back where they belong.
    6. Plug in the vacuum cleaner.
    7. Vacuum the floor.
    8. Unplug the vacuum cleaner.
    9. Dust the furniture.
    10. Wipe down the dining table.
  3. Cleaning the dining room

    1. Move into the kitchen.
    2. ...

The algorithm is still a large number of small steps, but organized this way, it becomes easier to see what the overall tasks are. The next thing you might notice is that a lot of the steps are really the same. I mean, in reality they are slightly different depending on what room you're in, but the steps themselves are common to every room you clean. We could express this better and eliminate this redundancy.

  1. Cleaning the living room

    1. Go into the living room.
    2. Clean the room:
      1. Pick up trash lying around.
      2. Put trash in the garbage.
      3. Pick up things off of the floor.
      4. Put things back where they belong.
      5. Plug in the vacuum cleaner.
      6. Vacuum the floor.
      7. Unplug the vacuum cleaner.
      8. Dust the furniture.
  2. Cleaning the dining room

    1. Move into the dining room.
    2. Clean the room. (same as above)
    3. Wipe down the dining table.
  3. Cleaning the dining room

    1. Move into the kitchen.
    2. Clean the room. (same as above)

With this kind of "factoring", the redundant part of the algorithm doesn't need to be repeated in many different places. It makes things easier to read and understand. These are important goals in writing good programs.

From algorithm to program

Let's see what this same algorithm would look like in the form of a program. This will rely heavily on concepts from lesson 1. To start with, let's look at the first version before we made any improvements.

WScript.Echo("Go into the living room.");
WScript.Echo("Pick up trash lying around.");
WScript.Echo("Put trash in the garbage.");
WScript.Echo("Pick up things off of the floor.");
WScript.Echo("Put things back where they belong.");
WScript.Echo("Plug in the vacuum cleaner.");
WScript.Echo("Vacuum the floor.");
WScript.Echo("Unplug the vacuum cleaner.");
WScript.Echo("Dust the furniture.");
WScript.Echo("Move into the dining room.");
WScript.Echo("Pick up trash lying around.");
WScript.Echo("Put trash in the garbage.");
WScript.Echo("Pick up things off of the floor.");
WScript.Echo("Put things back where they belong.");
WScript.Echo("Plug in the vacuum cleaner.");
WScript.Echo("Vacuum the floor.");
WScript.Echo("Unplug the vacuum cleaner.");
WScript.Echo("Dust the furniture.");
WScript.Echo("Wipe down the dining table.");
WScript.Echo("Move into the kitchen.");
WScript.Echo("...");

Remember from the first lesson that Echo just displays some text. You can save this program in a file (like c:\cleaning1.js) and run it using cscript to see the output, which, after the problem set in lesson 1, hopefully you can already guess. If you can't guess it, I would recommend taking another look at lesson 1 and trying the problems again.

Improvement: adding structure and names

Our first step in improving our algorithm was giving it some logical structure by grouping tasks together and naming them in a way that makes sense. Recall from lesson 1 that a function is a series of statements that are executed together and which are given a name to call them by. We can use functions here to express the structuring I showed earlier.

function CleanLivingRoom()
{
    WScript.Echo("Go into the living room.");
    WScript.Echo("Pick up trash lying around.");
    WScript.Echo("Put trash in the garbage.");
    WScript.Echo("Pick up things off of the floor.");
    WScript.Echo("Put things back where they belong.");
    WScript.Echo("Plug in the vacuum cleaner.");
    WScript.Echo("Vacuum the floor.");
    WScript.Echo("Unplug the vacuum cleaner.");
    WScript.Echo("Dust the furniture.");
}

function CleanDiningRoom()
{
    WScript.Echo("Move into the dining room.");
    WScript.Echo("Pick up trash lying around.");
    WScript.Echo("Put trash in the garbage.");
    WScript.Echo("Pick up things off of the floor.");
    WScript.Echo("Put things back where they belong.");
    WScript.Echo("Plug in the vacuum cleaner.");
    WScript.Echo("Vacuum the floor.");
    WScript.Echo("Unplug the vacuum cleaner.");
    WScript.Echo("Dust the furniture.");
    WScript.Echo("Wipe down the dining table.");
}

function CleanKitchen()
{
    WScript.Echo("Move into the kitchen.");
    WScript.Echo("...");
}

CleanLivingRoom();
CleanDiningRoom();
CleanKitchen();

You could save this in a file (I'd call it c:\cleaning2.js) and run it with cscript, and you will see the same output as cleaning1.js. I will briefly reiterate some of the important things I talked about in the first lesson.

The first part of this program defines functions, that is, gives the function name, parameters (in this case, there are no parameters; that is permitted), and body.

The second part of the program (just the last three lines) calls the functions that were just defined, which causes the computer to jump into the function body of each function, run the lines of code within it, and jump back to the next line.

In terms of readability, you may be able to tell that doing something as simple as giving names to tasks and grouping tasks together makes things in general much easier to see. Imagine for a moment that CleanLivingRoom, CleanDiningRoom, and CleanKitchen were actually defined in some other file, so their function bodies were invisible to you. Even with this diminished knowledge of their specifics, just by reading their names you can get an overall sense for what the functions would do if you called them. Learning how to structure programs this way is a very important technique in programming.

Improvement: reducing redundancy

The second improvement we made in the algorithm above was not listing out all of the steps for cleaning a room each time -- reducing redundancy. We can do the same thing with our sample program too.

function CleanThisRoom()
{
    WScript.Echo("Pick up trash lying around.");
    WScript.Echo("Put trash in the garbage.");
    WScript.Echo("Pick up things off of the floor.");
    WScript.Echo("Put things back where they belong.");
    WScript.Echo("Plug in the vacuum cleaner.");
    WScript.Echo("Vacuum the floor.");
    WScript.Echo("Unplug the vacuum cleaner.");
    WScript.Echo("Dust the furniture.");
}

function CleanLivingRoom()
{
    WScript.Echo("Go into the living room.");
    CleanThisRoom();
}

function CleanDiningRoom()
{
    WScript.Echo("Move into the dining room.");
    CleanThisRoom();
    WScript.Echo("Wipe down the dining table.");
}

function CleanKitchen()
{
    WScript.Echo("Move into the kitchen.");
    CleanThisRoom();
}

CleanLivingRoom();
CleanDiningRoom();
CleanKitchen();

You can save this program in a new file (say, c:\cleaning3.js) and run it with cscript. You should see the same output as the previous two iterations of the program.

Much the same way as I described it in the algorithm, we have now written down in just one place the steps needed to clean a single room (the function we just defined, called CleanThisRoom), and anywhere we need to do those steps, we can call that function. Now if we needed to continue adding rooms to be cleaned, it's a matter of adding just a few more lines. With a house of 20 rooms, the program would still be quite readable. Compare this mentally to what the original unstructured program would look like; that one would be over a hundred lines!

The thought process for writing and refining algorithms

Now that we've looked at an algorithm and its corresponding program in practice, I want to go back to algorithmic thinking. It is really about following all of the steps we did before:

  1. Think of the problem you need to solve
  2. Express very explicitly all of the steps you feel you need to do to solve the problem. Err on the side of being more verbose. It's easier to work with too many steps than too few.
  3. Step back and try to identify patterns or structure in the set of steps you came up with. Look for groups of things that are often or always done together, and put them into groups.
  4. Look across the groups and see whether any of them are similar or identical. In these cases, you can write something like, "now see steps for X".

I want to stress one thing, that we're doing these steps when examining the algorithm, not the program. That is, these are steps we can follow before writing the program code that will improve the way we go on to write it. At this time we're not applying them to the program code itself, though we may do that in a subsequent lesson.

One more program - drawing weird shapes

With those steps in mind, let's do one more example program. I like this particular example so much that I'm going to translate it almost verbatim from the CSE142 lecture where it was presented.

The problem: draw these weird ASCII art shapes

We want to write a program that can display these ASCII art shapes: a wand with a star on the end, a plain sword, and a serrated sword.

 *
 |
 |
 |

 |
 |
 |
 |)

 |>
 |>
 |>
 |>
 |
 |)

A simplistic algorithm to draw the shapes

Let's go through the steps above when considering how to solve this problem. First, let's consider the problem. We have to draw a few different shapes. this will again consist of using Echo to display lines of text that make up the drawings.

Next, let's write down explicitly the steps needed to solve the problem. At this stage, I'm not going to consider too much structure or examine patterns; that will come later.

  1. Draw a *
  2. Draw a |
  3. Draw a |
  4. Draw a |
  5. Draw a blank line
  6. Draw a |
  7. Draw a |
  8. Draw a |
  9. Draw a |)
  10. Draw a blank line
  11. Draw a |>
  12. Draw a |>
  13. Draw a |>
  14. Draw a |>
  15. Draw a |
  16. Draw a |)

So basically, every step is just drawing the corresponding lines from the figures. That might seem like a pretty simple solution, but it is one that works.

Identifying structure in the simplistic algorithm

Now let's go through and look for structure. Well, since I told you what these figures are earlier (a wand with a star on the end, a plain sword, and a serrated sword), maybe it's worth breaking them apart into their constituent parts.

The wand is made up of two parts, primarily: the star on the end and the shaft. Likewise, the sword is made up of the blade and the handle. The serrated sword is also made up of a blade and a handle, though the blade looks a little different. So maybe we can propose the following set of more general steps.

  1. Draw the wand:

    1. Draw the star
    2. Draw the shaft
  2. Draw a blank line
  3. Draw the plain sword:

    1. Draw 3 pieces of plain blade
    2. Draw the handle
  4. Draw a blank line
  5. Draw the serrated sword:

    1. Draw the serrated blade
    2. Draw a piece of plain blade
    3. Draw the handle

Compared to the set of steps above, this is much easier to grasp intuitively, which again comes back to the benefit of identifying structure in your algorithms.

Removing redundancy from the structured algorithm

I'm going to go through the algorithm again and color some of the things I feel are common.

  1. Draw the wand:

    1. Draw the star
    2. Draw the shaft
  2. Draw a blank line
  3. Draw the plain sword:

    1. Draw 3 pieces of plain blade
    2. Draw the handle
  4. Draw a blank line
  5. Draw the serrated sword:

    1. Draw the serrated blade
    2. Draw a piece of plain blade
    3. Draw the handle (see above for steps)

You may be a little surprised here that I didn't consider drawing the wand's shaft to be similar to drawing the blade of the sword. It's true that in ASCII art they look the same, but in an abstract sense, they're not similar. A wand's shaft looks nothing like a sword blade, so intuitively, why would I use the same program code to draw them? Imagine for a moment that we were making a program to draw these things not in ASCII art, but with more realistic 3D rendering. There's no way our algorithms to draw those two would be similar; we'd never consider them common. Likewise, it's not suitable to do that with this algorithm.

You might come up with a counterargument for sword handles, that different swords could have very different types of handles. But you have to admit that there are situations in which drawing them could be the same. We're going to assume here that these handles do look alike, for the purpose of this problem.

So all in all there isn't too much redundancy in this program. That's fine; all programs are different, and have different amounts and types of redundancy.

Finally, just to sum up the point of this whole lesson, I want to reiterate that functions are a tool that can be used to help break down larger tasks with many steps into smaller groups of tasks. You can name functions to help make your program code easier to read. You can call functions multiple times to reduce redundancy in your programs. When trying to write a program to solve a problem, it's best to think about the overall problem, then break it up into smaller tasks, then try to identify patterns to reduce the redundancy. It's best to follow through with this thought process before writing the program code.

Problems and solutions

Problem 1: write the program code to draw the wand and swords above. We've already thought about the algorithm, so it's more about translating it into some function definitions and function calls. It's worth noting that you might get a somewhat different solution than I did, based on how you decided to name the functions, etc.. That's okay.

Problem 1 solution - click to show or hide

// the star at the top of the wand
function DrawWandStar()
{
    WScript.Echo(" *");
}

// part of the handle of the wand
function DrawWandShaftPiece()
{
    WScript.Echo(" |");
}

// draw the entire shaft, piece by piece
function DrawWandShaft()
{
    DrawWandShaftPiece();
    DrawWandShaftPiece();
    DrawWandShaftPiece();
}

function DrawWand()
{
    DrawWandStar();
    DrawWandShaft();
}

// draw a piece of plain blade
function DrawPlainBladePiece()
{
    WScript.Echo(" |");
}

// draw the whole plain blade
function DrawPlainBlade()
{
    DrawPlainBladePiece();
    DrawPlainBladePiece();
    DrawPlainBladePiece();
}

function DrawSwordHandle()
{
    WScript.Echo(" |)");
}

function DrawPlainSword()
{
    DrawPlainBlade();
    DrawSwordHandle();
}


// a piece of the serrated blade
function DrawSerratedBladePiece()
{
    WScript.Echo(" |>");
}

// draw the whole serrated blade in pieces
function DrawSerratedBlade()
{
    DrawSerratedBladePiece();
    DrawSerratedBladePiece();
    DrawSerratedBladePiece();
}

function DrawSerratedSword()
{
    DrawSerratedBlade();

    // remember from the reference drawings that there is one piece of
    // plain blade here
    DrawPlainBladePiece();
    DrawSwordHandle();
}

// the part of the program where we actually call those functions
DrawWand();

WScript.Echo("");

DrawPlainSword();

WScript.Echo("");

DrawSerratedSword();

Problem 2: add to your program above, to draw the Keyblade:

 |=
 |
 |
 |
[|]

Your additions may result in modifications to functions you wrote earlier, or change function names. You may introduce new functions. You may call functions that you had already created for problem 1.

Problem 2 solution - click to show or hide. I have highlighted the new parts that were added to answer problem 2.

// the star at the top of the wand
function DrawWandStar()
{
    WScript.Echo(" *");
}

// part of the handle of the wand
function DrawWandShaftPiece()
{
    WScript.Echo(" |");
}

// draw the entire shaft, piece by piece
function DrawWandShaft()
{
    DrawWandShaftPiece();
    DrawWandShaftPiece();
    DrawWandShaftPiece();
}

function DrawWand()
{
    DrawWandStar();
    DrawWandShaft();
}

// draw a piece of plain blade
function DrawPlainBladePiece()
{
    WScript.Echo(" |");
}

// draw the whole plain blade
function DrawPlainBlade()
{
    DrawPlainBladePiece();
    DrawPlainBladePiece();
    DrawPlainBladePiece();
}

function DrawSwordHandle()
{
    WScript.Echo(" |)");
}

function DrawPlainSword()
{
    DrawPlainBlade();
    DrawSwordHandle();
}


// a piece of the serrated blade
function DrawSerratedBladePiece()
{
    WScript.Echo(" |>");
}

// draw the whole serrated blade in pieces
function DrawSerratedBlade()
{
    DrawSerratedBladePiece();
    DrawSerratedBladePiece();
    DrawSerratedBladePiece();
}

function DrawSerratedSword()
{
    DrawSerratedBlade();

    // remember from the reference drawings that there is one piece of
    // plain blade here
    DrawPlainBladePiece();
    DrawSwordHandle();
}

// top of the keyblade
function DrawKeybladeEnd()
{
    WScript.Echo(" |=");
}

function DrawKeybladeHandle()
{
    WScript.Echo("[|]");
}

// draw the top, then the plain blade portion in the center, then the handle
function DrawKeyblade()
{
    DrawKeybladeEnd();
    DrawPlainBladePiece();
    DrawPlainBladePiece();
    DrawPlainBladePiece();
    DrawKeybladeHandle();
}

// the part of the program where we actually call those functions
DrawWand();

WScript.Echo("");

DrawPlainSword();

WScript.Echo("");

DrawSerratedSword();

WScript.Echo("");

DrawKeyblade();