Lesson 6 - Matts_Toolbox.py

In this lesson, I am going to briefly go over my toolbox that I use for all monkey programs at the LRC. This is an essential script file that contains many useful monkey program functions, such as pseudorandomization, cursor movement, and pellet dispensing. As I say in the video, feel free to look at it, but you will NOT need to code this on your own. You can just use mine 🙂

 

I want to give special thanks to Julia Watzek who was the original author of this script, I have simply modified it to better fit our current programs.

Display Functions

These functions help establish the task screen. The first one defines how many pixels appear on the screen, which for the monkey solo computers are typically 800×600 pixels. The second function refreshes the screen clearing all icons, cursors, etc. from the screen.

Pyton
				def setScreen(full_screen=True, size=scrSize):
    """Define screen with scrSize, no frame, and full screen. Option to set
       full screen = False for window display (for development)."""
    if full_screen:
        return pygame.display.set_mode(size, pygame.FULLSCREEN)
    else:
        return pygame.display.set_mode(size)

def refresh(surface):
    """Blit background to screen and update display."""
    surface.blit(surface, (0, 0))
    pygame.display.update()
			

Object Oriented Programming

In this next part of the code we are going to define one of the most important Classes in the toolbox. Classes are a part of what is known as Object-Oriented Programming (OOP). For right now, you do NOT need to know how OOP works or even what it is, we will get to that later! For now, just know that OOP allows us to create entities in our program (called Objects) all of which share a list of traits (defined in a Class). 

 

Classes: A chunk of code that defines all the traits and functions of a category of objects

Objects: Entities in our code that inherit all the traits and functions defined in the class

 

 

Think of classes like a category. For example, “Dog” is a class. All dogs have fur, eye color, height (traits), and they all have the ability to bark, wag their tail, and lick (functions). These traits and  functions are inherited by all the dog objects we create, but they can vary by different types of dogs. A specific dog would be an “object”. Let’s create two objects of the class “dog”: 

 

 

1. Great Dane

2. Chihuahua

 

 

Both of these “objects” are of the “class” dog, and thus they both inherit the traits and functions defined in the class “dog”. When we create these objects, we assign values to all these traits. For example, a Great Dane object would have a higher value for height than the Chihuahua’s value of height, but maybe they share the same eye color of “brown”.

 

 

The Box Class

In this section of the code we create a class known as “Box.” This class is essentially the category used to create objects on a screen. Whether that is a start button, a cursor, or an icon on a screen, all of these objects are a part of the class “Box.” 

We initialize the class and give it the following traits: size, color, speed, circle, image, image color, rectangle size, rectangle center, and a mask. We will get more into the specifics of these traits later

 

Pyton
				class Box(pygame.sprite.Sprite):
    def __init__(self, size=(20, 20), position=(400, 300), color=(black), speed=8, circle=False):
        super(Box, self).__init__()
        self.size = size
        self.color = color
        self.speed = speed
        self.circle = circle

        self.image = pygame.Surface(size)
        self.image.fill(color)
        self.image.set_colorkey(white)
        self.rect = self.image.get_rect()
        self.rect.center = self.position = position   # self.position and self.rect.center are the same

        # If the image is actually a circle, we set circle = True (Useful for the cursor)
        if self.circle == True:
            self.image.fill(white)
            pygame.draw.ellipse(self.image, self.color, (0, 0, self.size[0], self.size[1]))

        self.mask = pygame.mask.from_surface(self.image)
        # Mask creates a 'mask' object over the Box object basically making transparent parts of the sprite untouchable
			

Box Functions

Here are the functions we give to Objects of the Class “Box”. You define functions using the following format. First you define the function name, and then in parentheses you put the arguments that the function needs to run. You always include (self) to indicate the function is being called on the object itself, otherwise the program has no idea who is running the function.

 

For example, maybe for our dog class we want to make a function that allows our dog objects to bark. We define the function name as “bark” and then require the function to have both the “self” argument and the “decibel” argument. When we call the function later on, we need to specify how loud the dog is going to bark. 

 

EX: chihuahua.bark(10) will cause the chihuahua to bark at 10 dBs

Pyton
				    def function_name(self, trait1):
        self.trait1 = trait1

    # EXAMPLE for DOG CLASS
    def bark(self, decibel):
        self.loudness = decibel
			

update()

This function updates the Box object’s traits to whatever you tell it.

Pyton
				    # Updates box size, color, position, and speed
    def update(self, size=None, color=None, position=None, speed=None):
        self.size = size or self.size
        self.color = color or self.color
        self.position = position or self.position
        self.speed = speed or self.speed

        self.image = pygame.Surface(self.size)
        self.image.fill(self.color)
        self.image.set_colorkey(white)
        self.rect = self.image.get_rect()
        self.rect.center = self.position

        if self.circle:
            self.image.fill(white)
            pygame.draw.ellipse(self.image, self.color, (0, 0, self.size[0], self.size[1]))

        self.mask = pygame.mask.from_surface(self.image)
			

draw()

This function draws the object on the screen

move()

This function allows the object to move x number of pixels horizontally and y number of pixels vertically at whatever the object’s speed is

mv2pos()

This function allows you to instantly move an object to an X, Y coordinate on the screen

Pyton
				    # Draws the box onto display/screen assigned with setScreen().
    # In this case, set window in setScreen
    def draw(self, surface):
        surface.blit(self.image, self.rect)

    # Move box x pixels to the right and y pixels down.
    def move(self, x, y):
        self.rect.move_ip(x * self.speed, y * self.speed)
        self.rect.clamp_ip(scrRect)
        self.update(position=self.rect.center)

    # Move box to position (x, y)
    def mv2pos(self, position=None):
        self.update(position=position)
			

collides_with()

This function checks if your object is colliding with another object. It does this by seeing if the pixels from your one object (i.e. the pixels of the cursor) are overlapping with the pixels of an icon the monkey is trying to select. If it is, then it returns True, if it is not, then the function returns False.

collides_with_list()

This function checks if your object is colliding with another object that is inside of a list of stimuli. It does this by seeing if the pixels from your one object (i.e. the pixels of the cursor) are overlapping with the pixels of any other icon in a list. This function then returns the value of the object it is colliding with. For example, if the cursor collides with stimuli #231, then it returns a value of 231 to the computer. 

 

This function is very useful if your task has hundreds of stimuli, so you do not have to write a single line of code for each possible collision. Instead you can call this function and write task contingencies based on what number is returned.

Pyton
				    # Tests for pixel-perfect collision with another Icon (sprite). Return True if contact occurs
    def collides_with(self, sprite):
        offset_x = sprite.rect.left - self.rect.left
        offset_y = sprite.rect.top - self.rect.top
        return self.mask.overlap(sprite.mask, (offset_x, offset_y)) is not None

    # Tests for pixel-perfect collision with an icon on the list
    def collides_with_list(self, list):
        for i, sprite in enumerate(list):
            if self.collides_with(sprite):
                return i                              # Returns the index of the icon if collision is occurring
        return -1                                     # Returns -1 when no collision is occurring
			

Moving the Cursor

Now that the Box Class is established and has been given several functions, now we need to tell the program how the monkeys can move the cursor with a joystick. The first thing we need to do is check if there is even a joystick plugged in. If there is not a joystick, then joyCount will be equal to 0. If there is one plugged in, joyCount will be equal to 1 (or the number of joysticks plugged in, which is useful for the joint computer)

Pyton
				joyCount = pygame.joystick.get_count()              # ASKS THE COMPUTER IF THERE IS A JOYSTICK ATTACHED
if joyCount > 0:                                    # IF VALUE > 0 THERE IS ONE ATTACHED
    joy = pygame.joystick.Joystick(0)               # IT WILL TELL YOU THIS IS THE JOYSTICK YOU ARE USING
    joy.init()
    pygame.mouse.set_visible(False)                 # Hides the mouse on the screen
			

moveCursor()

This function defines how the cursor moves. Without getting too much into the details of how this function is coded, just know that this is a function which the program can call that allows someone to move the cursor around the screen. First, if joyCount is equal to 0 (no joystick plugged in) you can use the arrow keys to move the cursor. However, if joyCount is greater than 0 (a joystick is plugged in) then it uses the direction of the joystick to move the cursor. 

 

This function also allows you to restrict the cursor to only be able to move in certain directions. You do this by calling the moveCursor function with argument: only = “direction”. It also allows you to turn diagonal movement off which means they can only move up, down, left, right in straight lines. I have never used that before tbh…

Pellet Dispensing and Sound Playing

The next couple functions allow the program to dispense pellets and play sounds. 

pellet()

For this function, you simply call it and give it the number of pellets you want it to dispense. All the function is doing is going into the C: Drive, where pellet.exe is located, and run that .exe file the number of times you provided in the argument. If a pellet dispenser is NOT attached, it will print the word “pellet” to the Python Shell instead.

For example, pellet(4) will run pellet.exe 4 times.

Pyton
				def pellet(num=1):
    for i in range(num):
        if os.path.isfile(pelletPath):
            os.system(pelletPath)
            print(pelletPath)
        else:
            print("pellet")             # Prints pellet is pellet.exe cannot be found
        pygame.time.delay(500)
			

sound()

This function is super simple. Basically in your main program, you call the function sound() and either give it True or False as an argument. If you pass it True, it plays the monkey’s correct ding noise, if you pass it False, it plays the monkey’s incorrect buzzer noise.

Pyton
				def sound(sound_boolean):
    if sound_boolean:
        sound_correct.play()
    else:
        sound_incorrect.play()
			

Pseudorandomization

There are a couple other functions in the program, and you certainly should know them, but for sake of time I am going to skip over them. They are mostly just helper functions, like allowing you to hit “Esc” or “Q” to quit out of a program, allowing you to pull task parameters from a parameters.txt file, or writing a row of data into a .csv file. We will go over these more when we get into coding the programs. However, there is one function in here that is quite useful. This is the pseudorandomization function. This allows you to take an array of numbers and randomly shuffle them around so that no number appears more than 3 times in a row. This is VERY helpful for pseudorandomizing trial types.

pseudorandomize(array)

All you have to do when you call this function,  is tell it which array you are trying to randomize. Then it takes that array and randomly shuffles it around. Next it iterates through each number in the array and checks to make sure it is not the same as the previous 2 numbers. If it is, then it reshuffles the array.

 

WARNING: if you pass it an array with only one item (i.e. array[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), it will break the program and never run. If you pass it an array with only two items (i.e. array[1, 1, 1, 1, 2, 2, 2, 2]) it will almost always return the array in alternating fashion (i.e. array[1, 2, 1, 2, 1, 2, 1, 2, 1, 2]). Therefore, this function is best used with arrays that have three or more numbers.

Pyton
				def pseudorandomize(array):
    random.shuffle(array)
    new_array = array
    i = 2
    while i <= len(new_array) - 1:
        if new_array[i] == new_array[i - 1] and new_array[i] == new_array[i - 2]:
            random.shuffle(new_array)
            i = 2
        i += 1
    return new_array
			

Congratulations!! You made it through the Toolbox! Now its time to code our very first program!!!