Sunday, February 21, 2010

Tower of London

The Tower of London test  (TOL) is a cognitive planning test akin to the Tower of Hanoi (TOH), initially developed by Tim Shallice (see  Shallice, T. (1982). Specific impairments of planning. Philosophical Transactions of the Royal Society of London. Series B, Biological Sciences, 199-209.)   Unlike the TOH, it requires relatively less deep thinking, recursion, impasses, and so on, but it can still be quite a challenging problem solving task.  How does it work?



There seems to be some confusion over what the Tower of London really is.  Basically, you are given a stack of disks that are randomly arrayed in stacks or on poles, and typically a goal state showing the same disks in a new configuration.  You need to move the disks to the new configuration, with the only limitation that you can only move one disk at a time.  Unlike TOH, there are no limitations on order or size of the disks.  The original Shallice study used just three disks, and restricted the stack heights to be of depths 1, 2, and 3.  Later versions have also existed which give no stack depth restrictions, and allow more than just three disks.  Other versions may give specific instructions about movement (move red disk to the third pile), without specifying a complete location.  Ultimately, I will need to make some decisions about how much of the variability the experiment should support.

A while back, I implemented TOL in PEBL, with sort of limited functions.  It really wasn't a complete study yet, it had no stack height limitations, and so on.  Furthermore, I did this before I had mouse capabilities in PEBL, so you had to hit numbers to specify pickup and drop operations.  So, I want to update this to make it into a respectable test as a part of the PEBL Test Battery, with instructions and data saving, graded difficulty,  limitations to stack height  as originally used by Shallice, some animation in the movement,  as well as taking mouse input.

Here is a video of the test in action.  Some of the feedback happens very quickly, and is not well captured by the video.




First, I wanted to update the look.  It was using only 640x480 pixels, and it looks sort of cramped.  Also, the grey background gives it crude look.  So I spread things out a bit and put it on a black background.  I also added some rectangles that provide targets for mouse clicks, and enable variable stack heights.

The most interesting part of this process is converting a keyboard test to a mouse-based test.  In many programming paradigms, event-related behavior like waiting for mouse clicks completely kills the procedural programming notion.  The code to handle a click or a keystroke lives encapsulated in its own methods and functions.  This is great for developing applications, but is extremely challenging when developing experiments, which are usually  a specific sequence of operations.   I deliberately avoided this in PEBL.  So the original code mapped the three piles onto number 1, 2, and 3.  When it came time to  pick up a disk, this line handled the keyboard input:

    first <- ToNumber(WaitForListKeyPress(["1","2","3"]))

Then, you redraw the screen, and use this line to drop it:

    next <- ToNumber(WaitForListKeyPress(["1","2","3"]))

No input other than 1,2,3 is allowed here, and it returns a string telling you what the input was.  How do we do this with a mouse?

The method I use is quite similar. The function I use most frequently for this is called

  resp <-  WaitForClickOnTarget(targetlist,keylist)

How does it work?  You give it a list of graphical object (images, labels, etc), and another list with entries corresponding to the target list. So, if I have a list of 3 hot zones representing the three piles, I could just replace the above with:

    first <- WaitForClickOnTarget(hotzones, [1,2,3])
and
    next  <- WaitForClickOnTarget(hotzones, [1,2,3])

I don't have hotzones defined however.  The question is, what should the 'hotzone' be?  Just the top disk?  a rectangle around the disks?  If I use a rectangle, how high should it be?  Should the hotzone rectangles be visible or not?  This takes a little thought and experimentation.  I decided to make three rectangles, one around each pile.  And to enable limited stack heights, I make the height of the rectangle about the size of the number of disks that can be placed in a pile.  If you don't want any limitations, just make the height as big as the number of disks.  Now together with the background changes and new layout, this is what it looks like, with 6-high stacks available.


The black blank area on the right contains a text box that feedback and instructions can be written in.  So now, the basic task is to click on the rectangles around the bottom piles.  First, you wait for a click to pick up the target; then you wait for the click to put it down.

Next, I wanted to add some animation.  If the disk moves instantaneously between the piles and hand, it can be difficult to see what happened. What does it take to do this?  It is actually pretty easy.  I already have  the current location of the disks in screen coordinates.  Originally, a function computed where the new coordinates would be and placed them there.  Now, I want to just save the new coordinates to start with.  Then, compare the start to the end, and divide by the number of steps.  Finally, I animate by looping through each disk, and computing its location on each step. Here is a basic explanation:

startXY was just collected, it is a list of the current locations of each disk in the configuratation.
endXY was just calculated, but it was in the order of the disks in the configuration, not in the order of disk1..6. So sort by disk IDS:

 
  endXY <- SortBy(endXY,diskIDs)
Create a single structure that lists start and end location for each disk
  startend <- Transpose([startxy, endxy])
 
Compute the stepsize each disk needs to make to animate the change in steps frames.  Notice that steps specifies how many interleaving frames the animation uses.  If the animation works really fast or really slow on your computer, you can change it.

  stepxy <- []

  loop(i, startend)
   {
      start <- First(i)
      end <- Second(i)
      stepxy <- Append(stepxy,[(First(end)-First(start))/steps,
                               (Second(end)-Second(start))/steps])
   }
Create a master list containing the disks, their starting location, and their stepsize:
  diskstep <- Transpose([disks2,startxy,stepxy])
Now, animate by computing the position of each disk on each frame of the animation.
  step <- 1
  while(step <= steps)
  {
    loop(i, diskstep)
     {
        disk <- First(i)
        start <- Second(i)
        diff <- Third(i)
This moves the disk to the right location
        disk.x <- First(start) + First(diff) * step
        disk.y <- Second(start) + Second(diff) * step
    }
Don't draw a new frame until all of the disks are moved.
   Draw()   
}

Here is a video of the final test, with animation effect. I also use this animation between trials to give a strong indication that the disks are being reset.







Next, I wanted to add stack height restrictions.  This is fairly easy to do: I just need to create a global variable called gStackHeights to specify the limits.  For an unconstrained test with 6 disks, it could be:

gStackHeights <-[6,6,6]

or for a Shallice-like test it could be:

gStackHeights <- [1,2,3]

I set the height of the hotspot rectangles based on these heights, and then, whenever a move is made or a configuration is sampled, we simply need to check if the new height would be higher than the value in that column.

One final piece is allowing the experiment to choose standard testing configurations.  To do this, I made several special-purpose functions which specified test sequences and height restrictions from various past studies.  I ended up with 8 different options, which I give the experimenter the opportunity to choose between at the beginning of the experiment:

     "[1] Unconstrained pile heights, {3,4,5} disks, progressive difficulty, 30 trials"
     "[2] Unconstrained pile heights, {3,4,5} disks, Random presentation, 30 trials"
     "[3] Shallice pile heights [1,2,3], 3 disks, random difficulty, 30 trials."
     "[4] Phillips (1999) trials A (unconstrained piles, 5 disks, progressive difficulty, 8 trials)"
     "[5] Phillips (1999) trials B (unconstrained piles, 5 disks, progressive difficulty, 8 trials)"
     "[6] Phillips (1999) trials C (unconstrained piles, 5 disks, progressive difficulty, 8 trials)"
     "[7] Fimbel et al (2009) old: [1,2,3] pile heights, 3 disks, progressive difficulty, 15 trials)"
     "[8] Fimbel et al (2009) young: [1,2,3] pile heights, 3 disks, progressive difficulty, 35 trials)

 If you want to run one of these every time, you should probably hand-edit the script file to not allow the choice.  It is pretty easy to do, and instructions are given inside the file about how to do it.

Most of these updates are distributed in the version of TOL that appears with the PEBL Test Battery, Version 0.5  The latest version, which allows different stack heights, is currently only in development archive, and will only be available in the upcoming PEBL Test Battery 0.6.  But you can download a copy here.  Just save it in place of battery/tol/TOL.pbl.




4 comments:

h A r q 7 said...

hi..i would like to improve this task by adding some feedback (visual and auditory) to indicate any errors. may i know what software that you are using to develop this task?

Shane Mueller said...

All of the tests here are created with the software system called PEBL. Find out more at http://pebl.sourceforge.net.

Anonymous said...

Hi, very impressed with all the tests created on PEBL.
Having a bit of trouble with ToL though, it doesn't seem to accept the participant number I define in my batch file. Other tests seem fine.

Even if I specify a participant number in the launcher, the test still requires me to enter one. Any solutions?

Shane Mueller said...

There is code at the beginning of the ToL that asks for the subject number. The line is something like:

gSubNum <- GetSubNum(gWin)

This overwrites the value if entered on the command line. Some of the scripts check to see if you entered something on the command line and only ask during the test if you did not enter anything. I haven't implemented that check for the ToL. If you want to enter the subject code using the command line or batch file, just comment out that line with a # character at the start.