Chapter 1: A Simple Object

Introduction

This is a programming example of reasonable complexity. We are going to demonstrate object variable use, forking, and generic object use.

Our example is a wind-up toy. The basic things that a wind-up toy does is get wound, and then slowly wind its way down, hopping or rolling or twirling along.

Object Creation

First, let's create a couple of objects to work from.

@new $thing named Generic Wind-Up Toy
You create "Generic Wind-Up Toy ($thing_3)"

@rename $thing_3 to $windup_toy
Renamed $thing_3 (Generic Wind-Up Toy) to $windup_toy.

@new $windup_toy named "Heisenberg's Wind-Up Duck",hduck
You create "Heisenberg's Wind-Up Duck ($windup_toy_1)"

@rename $windup_toy_1 to $heisenbergs_duck
Renamed $windup_toy_1 (Wind-Up Duck) to $heisenbergs_duck.

We'll refer to these as $windup_toy and $heisenbergs_duck, the object names assigned by @rename.

Simple Wind

First, we need some way to tell if the toy has been wound up. We'll create a variable called "wound" on the generic toy. We also need to know if it is currently walking around, so we also define a variable called "going" that will be set to 1 while the duck is walking, and 0 when it is not.

@add-variable $windup_toy,wound = 0
Object variable $windup_toy,wound added with value 0.

@add-variable $windup_toy,going = 0
Object variable $windup_toy,going added with value 0.

Before we can add a command to the duck, we need to program the method behind the command. All ColdC commands are actually just syntax descriptions that allow a method to be triggered. We define this method as public, which means that a method on any other object may call this method. There are two other access levels: protected, which means that only methods on the current object and its descendants may call this method, and private, which means that only the methods defined on the object on which this method is defined may call it.

@program $windup_toy.wind_cmd +access=public
arg cmdstr, cmd, @args;
  
// Increment the wound value x 2
wound+=2;
  
// Tell the player e has wound the toy
sender().tell("You wind up the " + .name() + ".");
  
// Tell everyone else in the room the toy was wound
.location().announce(strfmt("%s winds up the %s.", sender().name(), .name()));
.

On the first line we defined the arguments that the wind method will be expecting. These arguments are passed in by the command parsing system which we will define a command for in the next step. The first argument (cmdstr) will contain the entire command as entered by the player. The second argument (cmd) will contain just the command name that was used to activate the method. The third argument (args) will be a list of any other arguments sent to the method. The args argument is preceeded by an '@' symbol to indicate that we want to use scatter-assignment, which means that the args variable will receive any further arguments passed as a list. This helps prevent errors if there are too many arguments sent for some reason.

Here we also see lines that begin with '//' -- these are comments. The code compiler will ignore these lines, but they are extremely useful for documenting your code and making it easier to read. Documentation is important, and performing this task in the code will help both you and anyone else who uses your code!

The wound variable refers to the wound variable we defined on $windup_toy (the Generic Wind-Up Toy). In a ColdC method, local variables (variables used only in the executing method) are defined with the 'var' keyword. If a method references a variable that has not been declared locally with the var keyword, then the method assumes the variable is a member variable of the current object.

The idea behind adding 2 to wound is that you can wind it a bunch of times to make it go longer.

Now let's add the command so we can activate the wind method:

@add-command "wind <this>" to $windup_toy.wind_cmd

Let's try it on our duck:

wind hduck
You wind up the Heisenberg's Wind-Up Duck.

Everyone else sees:

Heisenberg winds up the Heisenberg's Wind-Up Duck.

Simple Messages

Next, we want to make the duck actually moves around. Remember that we're doing all the programming on the generic toy, and using the specific instance of the duck to test things out. To be properly generic, we need to define different messages that can hold message strings for the motions of the toy. So let's define two messages. One for the toy to start moving, and another for it to print as it continues to wind down.

@def-msg $windup_toy:startup
Message $windup_toy:startup defined.

@def-msg $windup_toy:continue
Message $windup_toy:continue defined.

We used the Messages system to define these messages to give us some powerful messaging options that ColdCore offers. You'll see what I mean in the next section. Let's set values of these messages on the duck. Note that we are setting the message values on the instance (the duck), and not on the parent (the generic).

While we're at it, we should give our duck a description!

@msg hduck:startup=[$this] waddles about and starts rolling forward.
startup = [$this] waddles about and starts rolling forward.
  
@msg hduck:continue=[$this] swivels its neck and emits a >> Quack <<
continue = [$this] swivels its neck and emits a >> Quack <<
  
@describe duck as A yellow plastic duck with wheels at the bottom and a knob  for winding.
Description for Heisenberg's Wind-Up Duck ($heisenbergs_duck) set.

Simple Drop

We should be ready to roll now... We're going to have the wind up toy start up when the player drops it. This introduces another concept, that of specializing an existing method. The generic located object ($located) defines the method .did_move that is called after an object has successfully been moved from one location to another. Normally, if we type "drop hduck" the .did_move method on $located gets invoked for the duck (even though it's a few levels removed from $located). If we define a .did_move method on $windup_toy, then that is the first definition of .did_move encountered as the ancestor heirarchy is searched, so it is the one that gets executed (as opposed to the definition on $located). Now we don't want to have to completely recreate everything $located.did_move() does, we just want our special action to happen after the normal drop actions finish. In order to accomplish this, we use the pass() function.

This is the slipperiest concent in ColdC programming, yet the basis for the entire object oriented nature of the system. An easy way to think of pass is like another method call: it just does the "basic thing" that this method normally does. We can add our specialization code both before and after this basic thing. The arguments you put in the pass() call are just like calling the ancester's definition of the method with those arguments. More sophisticated users of pass may wish to change these arguments; for now, just take it as gospel and pass the same arguments up the ancestral heirarchy. Sometimes you won't want to use pass at all, if you don't want the basic thing to happen, but want to completely override the default behavior. This is OK, you just have to think about what you want when deciding whether and where to put the call to pass().

We want the dropping to happen before any of our startup, so we call pass() right away. We better check if the toy has been wound up, and not do anything special if it hasn't. Also, we want to make sure that the location where the toy used to be was a person (or body), and that the new location is a place (or room). After we have established that these conditions are true, we can tell the toy to start moving around... otherwise, we'll make sure that it is not moving after any other move.

  @program $windup_toy.did_move +access=protected
  arg mover, old_place;
  
  // Let ancestor handle normal tasks
  pass(mover, old_place);
  
  // Trigger walk event if dropping from person to room
  if (.location().is($place) && old_place.is($body) && wound)
    .go();
  else
    going = 0;
  .

Next we'll define the .go() method which will do the actual work of moving the toy around. When we define .go, we set the forked flag. This means that when this method is called, it splits into its own task, allowing the calling method/task to continue on its way as if this method has already finished. We do this so that you and other players in the room will see the 'dropped' message immediately, and not after the toy has finished walking around the room. We separate the two tasks, because they are two different ideas that will execute apart from eachother -- but the dropping just triggers the moving.

  @program $windup_toy.go +access=protected +flags=forked
  var i, vars;

  if (!going) {
    going = 1;
    vars = #[["$this", this().name()]];
    .location().announce(.eval_message("startup", $windup_toy, vars));
    for i in [1 .. wound] {
      $scheduler.sleep(15);
      if (going) {
        .location().announce(.eval_message("continue", $windup_toy, vars));
        wound--;
      } else {
        break;
      }
    }
    going = 0;
  }
  .

You see that in this method we use thw wound and going object variables. This way if the .go() method is called while it is already moving around, the method will ignore the second call because the going object variable will be logically true (1) while the toy is moving around, and logically false (0) when it is idle.

We also use the core object $scheduler here to tell the system to wait for the indicated number of seconds (15) before continuing on to the rest of the code. This is useful if you need to write code that is somehow time-related. The $scheduler object offers many very useful methods for handling timed execution of tasks.

Now, let's drop our duck and see how it works!

  drop duck
  You drop Heisenberg's Wind-Up Duck.
  Heisenberg's Wind-Up Duck waddles about and starts rolling forward.
  Heisenberg's Wind-Up Duck swivels its neck and emits a >> Quack <<
  Heisenberg's Wind-Up Duck swivels its neck and emits a >> Quack <<

It actually waited 15 seconds in between each of those messages. I tried it again, but this time I typed @tasks right away to see that the tasks had been scheduled:

  @tasks
  -- Suspended Tasks --
  TASK    TICKS   METHOD                  RESUMABLE BY
  210609  19985   $scheduler.sleep()      $scheduler $user_heisenberg