/*  Matthew's RoboTag 0.9 for Lego Mindstorms.  
 *
 *  written in NQC version 2.1 r1 -- 
 *                     Linux builds: 
 *
 *  Copyright (c) 1998-2000 Matthew Miller. You're free to use and modify
 *  this as you wish. You can also redistribute it, but please give me
 *  credit, and if you make any changes, I'd like to hear about them.
 *  Thanks!
 *
 *  mattdm@mattdm.org    
 *
 * The basic game:
 *
 *   Two robots run around a playing field. When one tags another, the
 *   "victim" must sit still for a few seconds.
 *
 * The playing field:
 *
 *   A big piece of paper, with a think black line for a border. There can
 *   also be other lines on the paper for interest, although too many
 *   doesn't work very well -- these aren't very smart robots. I find that a
 *   simple partial line in the middle works well, like this:
 *
 *       -------------------------------------------------------
 *      |                                                       |
 *      |                                                       |
 *      |                         |                             |
 *      |                         |                             |
 *      |                         |                             |
 *      |                         |                             |
 *      |                         |                             |
 *      |                                                       |
 *      |                                                       |
 *       -------------------------------------------------------
 *
 *   The line is the border -- if the robot senses it, it must turn around.
 *   
 *   The only actual objects allowed on the field are the robots, so if one
 *   hits something, it's safe to assume it was the other robot.
 *
 * The robots:
 *
 *  Very simple. Treaded bulldozer-like robots, except that the 'blade' is
 *  connected to a touch sensor, so that if it's bumped, the sensor is
 *  triggered. (Shock absorbers, either large or small, are invaluable for
 *  making this work. I'm really surprised Mindstorms doesn't come with any,
 *  since it's such a useful construction. How do other people make their
 *  bump sensors broad enough to be useful?) The light sensor is mounted
 *  front-center and looks for the black line.
 *
 *  Light sensor is on input 2, touch sensor on 1. Motors on A and C. No
 *  particular reason -- that's just the Way It Is.
 *
 *  The treads, by the way, have a 24t gear on attached directly to their
 *  axles, and that meshes with an 8t gear attached to the motor shaft. This
 *  gearing works well and is nice and compact. I wouldn't recommend it on
 *  shag carpet or a dirty floor though, as the 24t gears are large enough
 *  and close enough to the ground that they can gather junk. Works fine on
 *  a paper play surface though.
 *  
 *  (I'll post pictures and a movie of these sometime soon.)
 *
 * The protocol:
 *
 *  A lot of trust involved here. The RCXs are too dumb^H^H^H^H simple to do
 *  otherwise. So, each robot believes what the other one says, and in fact
 *  relies on the other to know if _it_ has been hit.
 *
 *  If robot A hits robot B, A sends out a "Bang" signal. When robot B hears
 *  this, it responds with "Ow" and goes into "punishment" mode -- sits
 *  still beeping mournfully for a few seconds. If robot A hears this Ow, it
 *  goes about its business. (Namely, meandering meaninglessly.) But if it
 *  doesn't get the response after a few seconds, it starts spinning and
 *  yelling "Ditto Ditto Ditto" until a response is heard. (Spinning 'cause
 *  that has a good chance of making the IR ports line up, the lack of which
 *  being the usual cause of lost messages.) If robot B hears a Ditto, it
 *  responds with an Ow, and if it isn't in punishment mode already, starts
 *  being punished. If already is being punished, it doesn't add to the
 *  punishment (but still says Ow, just to get robot A to shut up).
 *
 *  This may sound confusing, but it works beautifully.
 *
 *
 * Notes:
 *
 *  Right now both robots run the same code. Maybe someday they'll each have
 *  their own independent devious schemes....
 *
 *  For some reason, one light sensor gives readings about 10 lower than the
 *  other. Which is sort of a pain. Originally, I just used a #define and
 *  changed it before compiling/downloading, but that's annoying. So now the
 *  robot reads 'normal' at startup, and assumes the dark line is going to
 *  be about (at least) 7 darker than that. This is good for other reasons
 *  too -- you could play on different colored paper without recompiling. 
 *  Future (fairly easy) adjustment -- make it so it also triggers on 7 (or
 *  so) brighter than normal, for use with tape on carpets.
 *
 *  When I say "we", I mean Paul Stauffer, who owns the other RCX. I wish I
 *  were that wealthy.
 *
 *  When we first built the robots, my bump-sensor kinda sucked and was
 *  breaking all the time and stuff. So I re-built it, and now it's really
 *  good (I'm proud of myself; can you tell?). It's especially good at
 *  making Paul whine, because in a head-on collision, my robot always
 *  triggers first, and therefore wins. Hahahahahahaha.
 *
 */
 

int randnum;
int bumpcount;
int needackflag;
int gotackflag;
int blackvalue;

task main()
{
    //set up sensors properly
    SetSensor(SENSOR_1,SENSOR_TOUCH);
    SetSensor(SENSOR_2,SENSOR_LIGHT);

    //make sure we're in long-range mode
    SetTxPower(TX_POWER_HI);

    initlight(); //set trigger-value for border lines, since apparently
                 //different sensors give different readings, and
                 //because different surfaces are obviously different.

    //clear all flags
    bumpcount=0;
    needackflag=0;
    gotackflag=0;

    // start message-watching service
    start postoffice;

    // start black-line-watching service
    start checkline;

    // start bumper-watching service
    start checkbump;

	// start going
	goforward();

    while(true)        //loop forever
    {
        if (bumpcount>0)        // if we got hit
        {
            punishment();     // sit still for a while
            bumpcount -=1;    // and then decrement hit counter
        }

        
        if (needackflag>1 && gotackflag==0) // if we need to hear ack of
        {                               // our bump signal and we haven't yet
            startspinning(); //start turning a random dir
            ClearTimer(0);   //count how long we've spun
            while(gotackflag==0)  //wait until we get the ack signal
            {                     
                SendMessage(34);  //yell ditto
                PlaySound(0);     //and beep nicely
                    
                Wait(10);   // if we yell too fast, we can't listen. 
                            // so we have to pause for a while. 5 was
                            // too little -- 10 works. haven't bothered
                            // to find the exact value

                if (Timer(0)>100) 
                {  // it's been a long time
                    gotackflag=1;   //pretend we got an ack
                    needackflag=0;  //and stop caring.
                }
            }

            //ok done spinning now go.
            goforward();
        }
    }
}

// this task watches for incoming messages.
task postoffice()
{
    ClearMessage(); //start by erasing whatever's in the air
    while(true)
    {
        if (Message()==33)  //bang! we've been hit!
        {
            SendMessage(6); //send ack
            bumpcount +=1;  //increase our damage. ow.
            ClearMessage(); //and of course clear the message now that
                            // we've dealt with it.
        }
        if (Message()==34)  //we've been hit but didn't respond. bad us.
        {
            SendMessage(6);  //ack
            if (bumpcount==0) // but only do anything if we're 
            {                 // supposed to be paying attention. if we're
                              // down already, we ignore " signals. 
                bumpcount +=1;
            }
            ClearMessage();
        }
        if (Message()==6)  //an ack
        {
            if (needackflag>0)  //we only care if we're listening.
            {
                needackflag=0;  //ok, so we don't care anymore
                gotackflag=1;   //and we have what we're lookin' for
            }
            ClearMessage();
        }
    }
}

task checkline()
{
    while(true)
    {
        if (SENSOR_2 < blackvalue)  //if the sensor detects dark
        {
            PlayTone(442,10);
            revturn();  //back up and turn
            goforward();  //then go forward again
        }
    }
}

task checkbump()
{
    while(true)
    {
        if (SENSOR_1 == 1)  //if the bump sensor is pressed
        {
            SendMessage(33); //yell bang
            PlaySound(0);

            gotackflag=0;    //clear old acks -- they're not relevant anymore
            
            needackflag=1;   //we need an ack now
            revturn();       //back up and turn
            goforward(); //the forward again

            if (needackflag==1)  //if we haven't gotten an ack yet...
            {
                needackflag=2;  //then advance to spinning around yelling
            }
        }
    }
}



sub revturn()  //back up and turn a random direction
{
    Fwd(OUT_A + OUT_C);  //back up
    Wait(100);                    //for one second

    startspinning();
    
    Wait(50);                   //keep turning for .5+Random(3) seconds 
    Wait(Random(300));      
    Off(OUT_A + OUT_C);         //stop motors
}


sub punishment()   //bad robot. must sit still for 8 seconds. 
{
    stop checkline;   // ignore everything else. we're DEAD. for now at least
    stop checkbump;   //    note that we still process messages.

    Off(OUT_A + OUT_C);

    repeat(4)
    {
        PlaySound(4);  //cry
        Wait(200);
    }

    goforward();       // ok go forward again.

    start checkline;  //and pay attention to stuff
    start checkbump;
}


sub initlight()   //reads the value of 'normal', and assumes that the 
{               // black-line border is about 7 darker. no real reason to
                // put this in a sub, except for neatness.

        blackvalue=SENSOR_2;
        blackvalue-=7;  //may have to adjust this. 
                        //also -- what about white lines?
}

void startspinning()  
{                   

    Off(OUT_A + OUT_C);

    randnum=Random(2);
    if (randnum == 1)
    {
        Rev(OUT_A);
        Fwd(OUT_C);
    }                 
    else
    {
        Rev(OUT_C);
        Fwd(OUT_A);
    }
}


void goforward()
{
    // (um, the motors are connected backwards, so
    // Rev=Forward. sorry)

    OnRev(OUT_A + OUT_C);
}

void goback()
{
    // (um, the motors are connected backwards, so
    // Rev=Forward. sorry)

    OnFwd(OUT_A + OUT_C);
}





Wednesday, 08-Mar-2000 14:51:04 EST