Spybots Smart Parts  
  The Unofficial Resource Centre for Lego Spybotic  
   
img  
   
   

TUTORIALS

TUTORIALS

advanced

home brew energy crisis- mindscript

Tutorial Details:
Difficulty Level: Intermediate
Topics Covered: Re-creating the Energy Crisis mission using MIndscript.
Assumed Knowledge: Most Basic and Intermediate topics.
Written By: BILL LANE

BACK

The aim of this tutorial is to look at the steps involved in creating a basic Spybot mission by re-creating Energy Crisis. It may seem that I'm re-inventing the wheel by re-creating an existing mission. But I have often used the process of imitation as a good starting place to learn a new skill. For a start I have a model I can easily refer to that will tell me how I'm going. The other thing is I know what I'm attempting is possible.

So where do we start? I started by making a list of the things that need to happen. My list looked like this:

  • Calibrate light
  • Detect bump to start
  • Detect bump if obstacle hit
  • React to obstacle hit
  • Detect light when mission achieved
  • Perform the victory dance
  • Detect low time
  • Respond to low time
  • Detect time up
  • Respond to time up
  • Random shocks

Note that I've seperated events and their respective responses. Each of the events will be monitored by a watcher. But I'm not going to let the watcher respond itself. Instead I'm going to maintain a variable that keeps track of whats happening in the mission and let the watchers make changes to that variable. That way there will be less chance that our outputs will receive conflicting instructions. So one of the first things I want to do is to declare and set a global variable to keep track of the current state of play. I'm going to need a total of 5 variables and 2 constants. So let's declare them now and I can explain them as required:

var gv_energyLevel = 6
var gv_state = 0
var gv_shockTime
var gv_lastShocktime = 0
var gv_timeSinceShock = 0
const gc_nearTime = 500
const gc_GameTimeLimit = 600

I've started the mission with the state equal to zero. It will remain in this state until the touch sensor is tapped the first time. The other states are:

0 ready to start
1 running
2 hit object
3 shocked
4 timeUp/out of energy
5 mission complete

The other thing to note from my initial list is that I haven't listed any controller events. In this mission we direct the Spybot in RC mode so we won't need to write any code to handle button presses.

Looking over my list I can see I'll need a few includes. Spybot.h declares my motors and sensors so I'll definitely need to include that. Globals.h declares all the light, sound and movement subs and constants; I'll definitely want that. Events.h declares the BumpEvent for the touch sensor and I'll need that. So I'll start by including those three headers:

#include<Spybot.h>
#include<Globals.h>
#include<Events.h>

Returning to my list the first item is Calibrate light. The Spybot will automatically calibrate the light sensor when we press the RUN button. So I don't need to do anything there. But I will want to set up an event and a watcher to detect that the light has been reached. Here's the event declaration:

event lighthigh when opto.normal

This goes after the includes but before the main task. The watcher goes after the main task and it looks like this:

watcher lightWatch monitor lighthigh{
if gv_state <> 0{gv_state = 5}
}

Note the watcher doesn't perform any actions. All it does is set the game state to 5 (mission complete) if the event occurs when the game state isn't 0 (we can't win if we haven't started). The only other thing we need to do to make the light sensor work is to start this watcher. This happens inside the main task:

start lightWatch

Next on the list is detect bump to start. The BumpEvent has already been declared in events.h so all we need to do is to set up a watcher:

watcher hit monitor BumpEvent{
select gv_state{
when 0{ start initGame}
when 1{gv_state = 2}
}
}

Here we use a select statement because we want the watcher to repond differently at the start of the game than it will during the game. If the sensor is hit while the state is 0 then the watcher will start the initGame task(we'll have a look at that in a moment). If the mission is running (gv_state = 1) then it will reset the mission to state 2 (hit object). When that's in place we will start this watcher in the main Task.

Now let's go back and have a look at initGame:

task initGame{
LED[iYellowBlink] = 0
LED[iYellowWarn] = 0
sound 3
display 5
wait 100
clear sound
clear display
start BioTick
gv_shockTime = random 150
start randomShockCounter //starts the random shock mechanism
gv_state = 1
}

This is one of three user defined (non watcher) tasks that I'm going to use. These tasks are declared afer the main task and before the watchers. The first two line turn off the ALERT light, play a sound and run the point to front LED animation. It then waits 1 second to let that finish and clears the sound and display. Next ti starts another task called BioTick. This task runs a forever loop that increments a counter (nBioTick declared in globals.h) 10 times every second. I'm going to use nBioTick as a kind of timer to watch for the end of the game. Here's the task:

task BioTick {
clear nBioTick
forever{
nBioTick += 1
wait 10
whatNext
}
}

This task also runs a sub-routine whatNext on every pass through the loop. Sub whatNext uses a select statement to check the value of gv_state and to respond as required. If we can consider BioTick as the missions heart then whatNext can be seen as it's brain. We'll take a closer look at whatNext soon. But first lets consider the rest of initGame. initGame's next command is to set another one of our global variables gv_shockTime to a random number between 0 and 150. This variable will tell the program when to give our Spybot a shock. This mechanism is controlled by another task randomShockCounter which is the next thing initGame starts.

N.B. When you look at the finished code you'll notice the command randomize at the top of the main task. This command resseds the random number generator. Using this command at the start of a program will get you a more random result.

Before we look at the randomShockCounter just note that initGame's final command is to set gv_state to 1 (the mission is now running).

task randomShockCounter{
clear nStateWatcher
forever{
nStateWatcher += 1
wait 10
if nStateWatcher = gv_shockTime{gv_state = 3 stop randomShockCounter}
}
}

Like BioTick the randomShockCounter runs a forever loop. This time incrementing another counter (nStateWatcher also declared in globals.h). The difference is that when this counter reaches the random value gv_shockTime this task resets the state to 3(shocked) and then shuts itself down.

There are two other events and their watchers that we haven't looked at. Both of these events relate to the nBioTick counter. One is triggered when time is up and the other is triggered when time is nearly up. Here are the event declarations:

event timeUp when nBioTick = gc_GameTimeLimit
event nearTime when nBioTick = gc_nearTime

Note the use of the two constants declared at the top of the program to test when the events should trigger. Now here are the watchers:

watcher timeUpWatch monitor timeUp{
if gv_state = 1{gv_state = 4}
}

watcher nearTimeWatch monitor nearTime{
if gv_state <> 0{timeShort}
}

The timeUp watcher resets gv_state to 4 (time up) and the nearTime watcher starts a sub-routine called timeShort.

sub timeShort{
local flashInt = gc_GameTimeLimit - nBioTick
LED[iYellowBlink] = 1
forever{
LED[iYellowBlinkInterval] = flashInt
repeat 10{sound 1 wait flashInt}
flashInt = gc_GameTimeLimit - nBioTick
}
}

Sub timeShort declares a local variable flashInt to control the frequency of a forever loop and the blink interval of the alert light. This variable is set to the differnce between the total time and the current time.The loop has a repeat that plays a short sound 10 times after which the flashInt variable is updated. The effect of this is that the sound is played more frequently the longer the loop continues. Indicating that timeUp is getting closer and closer.

You'll notice that there isn't much happening in the main task. It starts the watchers, does a few things like randomize, but that's all it does. Most of the action comes out of whatNext. So let's have a look at that next:

sub whatNext{
select gv_state{
when 1{lightUp}
when 2{hitObject}
when 3{shocked}
when 4{loseGame}
when 5{winGame}
}
}

Remember that whatNext is called by BioTick 10 times a second. When it's called it uses a select statement to check the value of gv_state and to call a suitable sub-routine based on it's value. When gv_state = 1( normal running) it calls a sub called lightUp:

sub lightUp{
if gv_state <> 0{
select gv_energyLevel{
when 6{LED[iDisplay] = 63}
when 5 {LED[iDisplay] = 55}
when 4 {LED[iDisplay] = 39}
when 3 {LED[iDisplay] = 7}
when 2 {LED[iDisplay] = 3}
when 1 {LED[iDisplay] = 1}
when 0 {gv_state = 4}
}
}
}

Sub lightUp checks the value of gv_energyLevel and updates the ARC lights based on how much energy remains. If no energy remains it resets the state to 4 (timeUp/out of energy).

Back in whatNext if the state value had been 2 (hit object) it calls sub hitObject:

sub hitObject{
try{
if gv_energylevel = 1{gv_state = 4 gv_energyLevel = 0}
else{
Set_Bead(SetRCDisable,1)
gv_energyLevel = gv_energyLevel - 1
Action(14,2, moveBackward,1,100)
BasicMove(moveStop,5)
Set_Bead(SetRCDisable,0)
gv_state = 1
}
}retry on fail
}

Sub hitObject checks how much energy remains. If we're on our last unit of energy it resets the game state to 4 (out of energy). Otherwise it subtracts 1 from the energy level, moves the Spybot backwards, plays a sound and displays an arclight animation before finally resetting the state to 1 (running) and exiting. But there are a couple of other things here worth comment. This is the first time I've actually tried to access the motors and you can see I've wrapped the whole thing in a try-retry statement. This ensures that there is no conflict with other subs trying to access the outputs. The other thing to note is the use of SetRCDisable. SetRCDisable controls whether a Spybot accepts controller commands. What I've tried to do is to tell the Spybot to ignore controller commands (Set_Bead(SetRCDisable,1)) at the top and then to accept all controller commands at the bottom. I've done this because you shouldn't be able to drive around casually when you should be recovering from a hit.

The sub called from whatNext when the state = 3 is shocked:

sub shocked{
try{
Set_Bead(SetRCDisable,1)
Action(6,2,moveShake,2,100)
BasicMove(moveStop,5)
gv_shockTime = random 150
start randomShockCounter
Set_Bead(SetRCDisable,0)
gv_state = 1
} retry on fail
}

This is similar to hitObject except that it resets the gv_shockTime value and then restarts randomShockCounter.

The sub called from whatNext when the state = 4 is loseGame:

sub loseGame{
try{
Set_Bead(SetRCDisable,1) //ignore controller
Action(sndCrash,2,moveBackward,1,100)
FancyMove(moveShake, 400)
wait 400
BasicMove(moveStop,5)
clear sound
clear display
LED[iYellowBlink] = 0
LED[iYellowWarn] = 0
Set_Bead(SetRCDisable,0)//respond to controller
stop Tasks
}retry on fail
}

Once again this is similar to the last two except that it turns off the alert light and then shuts down the program by using stop Tasks. Finally we come to winGame which is called by whatNext when the game state = 5:

sub winGame{
try{
Action(62,1, moveDance, 2,200)
wait 400
clear display
stop tasks
}retry on fail
}

As you can see winGame runs the victory dance before shutting down the program. Which illuminates the final dark corner of our program. Writing a program like this can at first appear a bit daunting. But the idea is to break the job down into small, relatively simple chunks. This will keep your code legible and therefore easier to debug. I haven't used a lot of comments (partly to keep a long tutorial as short as possible) but they'll also help to remind you of why you did what. Below is the full program. Copy and paste it into your script editor and you should have no trouble downloading and running it yourself.

Select All

 

This tutorial is protected by International Intellectual Property Rights laws and may not be reproduced or redistributed in full or part, without the prior written consent of the author. Unauthorized reproduction of this tutorial or its contents may result in prosecution.

 

 
 
DISCLAIMER: All content is provided as is, with no warranty stated or implied regarding the quality or accuracy of any content on or off this site. All trademarks, service marks, and copyrights are property of their respective owners. This site is not sponsored, authorized or sanctioned by the LEGO Group nor representative of their opinions in any way.PRIVACY POLICY