# Twins

Yeah, me again – same game, but a much more complicated problem now.

Two of my NPCs are twins. In my game, as in life, these twins are treated sometimes as individuals and sometimes as a group. How they are handled depends on whether or not they’re in the same location and doing the same thing.

Ex:

Vs.

After several attempts, I finally came up with something that seems to be capable of handling most of what I want; the twins each belong to a CollectiveGroup, and their ActorStates are all custom-made TwinStates designed to report its own specialDesc and distantDesc when the twin is unique, and nothing when they’re together and in “equivalent” states. The group, natch, has the opposite instructions:

``````
twinGroup: CollectiveGroup 'twins' 'twins'
t1State = twin1.curState
t2State = twin2.curState
desc()
{
"<<twin1.name>> and <<twin2.name>> <<twin1.lastName>> are identical twins. ";
if(t1State.isEqualTo == t2State)
t1State.dittoDesc;
else
inherited;
}
specialDesc()
{
if(t1State.isEqualTo == t2State)
specialDesc = t1State.dittoDesc;
else
specialDesc = nil;
}
distantSpecialDesc()
{
if(t1State.isEqualTo == t2State)
distantSpecialDesc = t1State.distantDittoDesc;
else
distantSpecialDesc = nil;
}
dobjFor(Examine)
{
verify() {}
preCond = [Twin1Seen, Twin2Seen]
}
;

class TwinState: ActorState
dittoDesc = "The twins are here. "
distantDittoDesc = "The twins are off in the distance. "
t1State = twin1.curState
t2State = twin2.curState
identicalStateCheck
{
if(t1State.isEqualTo == t2State)
{
specialDesc = nil;
remoteSpecialDesc = nil;
}
}
;

//Coding is then applied thusly:

twin1: Person 'Tweedle Dum' 'Tweedle Dum'
@beach2
isHim = true
isProperName = true '
desc="Tweedle Dum is a twin "
dittoDesc = curState.dittoDesc
distantDittoDesc = curState.distantDittoDesc
collectiveGroups = [twinGroup]
;

+t1rollerskate : TwinState
isInitState = true
stateDesc = "Tweedledum is rollerskating. "
dittoDesc = "The twins are both rollerskating. "
distantDittoDesc = "The twins are far away, rollerskating."
isEqualTo = t2rollerskate
;

/*
*t2rollerskate is an actorstate in the second twin.
*It does not contain the last 3 properties in t1rollerskate, because twin1 handles all of that coding.
*/

``````

/whew/

It’s probably sloppy, but it does most of what I want. However, there are some problems I can’t seem to hash out.

First of all, while the distantDesc for the CollectiveGroup is working just fine, its specialDesc is not reporting at all. At first I thought it was a coding error (All those brackets and parentheses and semicolons…) but when I replaced it with a simple doublequoted string, (eg., specialDesc = “WORK, YOU STUPID PROGRAM!!!”) I still got nothing. Is there something about collectiveGroups that I’m not aware of, or is there a flaw in my code?

Another problem which I’ve been having involves the Actors themselves. While I’m able to turn off specialDesc and distantDesc in favor of the Group descriptions, the twins are still being reported.

Ex:

How do I turn that off?

Does this all make sense? I can elaborate and provide more of my coding if needs be. I’m just so confused at the moment.

I can answer this question in two parts.

a) I don’t know.

b) Eric Eve will know.

Eric doesn’t, I believe, frequent this forum. But he does keep an eye on the newsgroup (rec.arts.int-fiction). If you don’t have newsgroup access through your ISP, you can post questions to it through Google Groups.

–JA

You’re not kidding!

Hey, while we may not have Eric Eve around, there are still a few of us who like to take a crack at problems like this. Don’t write us off yet!

That much is simple. Every object has a useSpecialDesc property that determines when it shows its specialDesc and distantSpecialDesc.

I also notice this:

I’m pretty sure those won’t give you the current states, since they’re set in the object rather than in any given function. Either move these lines into the functions where they’re used, or make them a function that returns the states.

Experimenting a little, it seems that collectiveGroups qualify as distant unless they’re actually physically present in the same room as the player. A little hack to work around this: have one of the twins constantly check the location of the collectiveGroup and move it if necessary.

Here’s a little something I cooked up:

[code]mergeTwins()
{
return (twin1.location==twin2.location && twin1.curState.isEqualTo==twin2.curState);
}

class TwinState: ActorState
isEqualTo = nil
;

twinGroup : CollectiveGroup, SecretFixture ‘twins’ ‘twins’ @StartRoom
"Jeunet and Pinon seem to be joined at the hip. "
specialDesc="The twins are here. "
useSpecialDesc=mergeTwins()
distantSpecialDesc="The twins are over there. "
;

StartRoom: Room ‘Start Room’
"This is the starting room. "
north=SecondRoom
;

+Me: Person
desc="You look like someone who is sick of twins. "
;

SecondRoom: Room ‘Second Room’
"This is the second room. "
south=StartRoom
;

DistanceConnector [StartRoom, SecondRoom] ;

twin1: Person ‘Pinon’ ‘Pinon’ @StartRoom
collectiveGroups = [twinGroup]
isProperName=true
useSpecialDesc=!mergeTwins()
dobjFor(Attack)
{
verify(){}
check(){}
action()
{
"You launch a seering verbal attack on Pinon. He becomes very, very angry. ";
setCurState(t1AngryState);
}
}
dobjFor(Push)
{
verify(){}
check(){}
action()
{
self.moveIntoForTravel(SecondRoom);
"You push Pinon and he rolls away in a well-meaning fashion. ";
}
}
afterAction()
{
if(twinGroup.location!=self.location)
twinGroup.moveInto(self.location);
}
isHim=true
;

+t1CaperingState: TwinState
stateDesc="Pinon is capering like a crazy fool. "
specialDesc="Pinon is here, capering away. "
isInitState = true
isEqualTo = t2CaperingState
;

+t1AngryState: TwinState
stateDesc="Pinon is fuming with rage. "
specialDesc="Pinon is standing here, smoke coming out of his ears. "
isInitState = true
;

twin2: Person ‘Jeunet’ ‘Jeunet’ @StartRoom
collectiveGroups = [twinGroup]
isProperName=true
useSpecialDesc=!mergeTwins()
dobjFor(Push)
{
verify(){}
check(){}
action()
{
self.moveIntoForTravel(SecondRoom);
"You push Jeunet and he rolls away in a well-meaning fashion. ";
}
}
isHim=true
;

+t2CaperingState: TwinState
stateDesc="Jeunet is capering like a crazy fool. "
specialDesc="Jeunet is here, capering away. "
isInitState = true
isEqualTo = t1CaperingState
;[/code]
Try pushing one of the twins to move him into the second room. Attack Pinon to change his ActorState.

I haven’t implemented distantSpecialDescs, but hopefully this is a start. Also, I’m not too sure if you really want the CollectiveGroup to be a SecretFixture as well, but I think you seem to have a better handle on actions and collections than I do.

Anyway, let us know how you get on!

A better handle than you do? Really? Heck, I’m just winging it.

The code looks useful, but I’m not sure of one thing: Where exactly is that mergeTwin() check located? It just seems to be floating there… I hate to ask a stupid question, but can you actually do that? o.O

Edit: Okay, I tried it, and it seems to work. Thanks again, Pacian!

You’re welcome.

In TADS 3 you can. Other OOP languages, such as Java and C# prevent you from having functions that aren’t part of an object. Generally, it’s probably something you want to avoid, especially for behaviour that you might want to override in instances or sub-classes of specific objects, but for exceptional cases and odd bits and bobs, it doesn’t hurt.

In particular, I think it’s good for things relating to text output or comparisons - things that might potentially be useful to call anywhere in your code.