Assignment 4: Artificial Agents
Contents
Assignment 4: Artificial Agents¶
This is a demo assignment that is available as part of the Introductory Python open materials.
Overview¶
This assignment covers Classes & Objects, and builds up to the application of Artificial Agents.
This assignment is out of 8 points, worth 8% of your grade.
Make sure your completed assignment passes all the asserts. This assignment also has some hidden tests - which means that passing all the asserts you see does not guarantee that you have the correct answer! Make sure to double check and re-run your code to make sure it does what you expect.
Setup¶
Code Imports¶
You must run the following cell before proceeding with the assignment.
# Setup - run this cell before doing the next part of the assignment
# This cell imports some extra code, making it available for us to use later
import random
import string
from time import sleep
from IPython.display import clear_output
Part 1: Functions & Methods (re-visited)¶
In this part, we will revisit and extend some work on using functions and methods.
We will also write some functions that we will use later in the assignment for the ‘bots’ application.
Q1 - String Methods (1 point)¶
First, we will explore some of methods from the str class.
a) join¶
During the last assignment you made a function that echoed a string several times with a seperator between each repetition.
Here we will achieve a similar goal through the use of the join() method of the str class.
When join() is called on a str object (let’s call it seperator) with a list as its argument, it joins the elements of the list, seperating each one by seperator.
Below we define a a list my_list. Use the join() method on the string you define as seperator = '~' to join the elements in my_list seperated by the '~' character.
Save the output to a new variable called joined_string. The output should look as follows:
'Python~is~so~much~fun'
# This variable provided for you
my_list = ['Python', 'is', 'so', 'much', 'fun']
# YOUR CODE HERE
raise NotImplementedError()
assert isinstance(joined_string, str)
assert joined_string == 'Python~is~so~much~fun'
b) replace¶
Now try to use the replace method to update the string statement such that it replaces UCLA to UCSD.
If you are unsure how use replace you can run str.replace? to look at documentation.
Note that using replace returns a new string that you need to assign to a variable if you want to keep a reference to it (replace is not ‘inplace’).
For this question, overwrite statement as the assignment to the output of the replace call.
# This code provided for you
statement = 'UCLA is the best UC in Southern California.'
# YOUR CODE HERE
raise NotImplementedError()
assert isinstance(statement, str)
assert statement == 'UCSD is the best UC in Southern California.'
c) replace for dropping characters¶
Use the replace method to remove all the exclamation points in the string excessive.
Assign the output of doing this to a variable called fixed.
Hint: you can drop characters with replace by ‘replacing’ them with an empty string.
# This variable provided for you
excessive = 'using!!! excessive!!! exclamation points!! is the best!!!!!!'
# YOUR CODE HERE
raise NotImplementedError()
assert isinstance(fixed, str)
assert fixed == 'using excessive exclamation points is the best'
d) Clearing all punctuation with replace¶
Now we want to generalize what we did in ‘c)’ to remove all punctuation using the replace() method.
Write a for loop to loop over every character in string.punctuation. Inside the loop, call replace on too_much with the current punctuation character to remove it, like we did in ‘c)’.
To do this, inside the loop, re-assign too_much to be the ouput of calling replace on too_much, so that you are replacing too_much with it’s updated version every time.
# This variable provided for you
too_much = 'I, think th@at!! punctuation... may?>> be the bes$$$t th!ing thats: ever! been! invented!!!!!!'
# YOUR CODE HERE
raise NotImplementedError()
assert isinstance(too_much, str)
assert too_much == 'I think that punctuation may be the best thing thats ever been invented'
Example: Functions with Default Values¶
Note that functions can also have inputs with default values.
This means you don’t necessarily need to provide an input with a specific value, since if you don’t provide it explicitly, it will take on the default value.
The following is an example of a function with a default value, provided to you, so you can see what it looks like.
You don’t need to do anything with this function.
def default_function(input_number, n_value=2):
"""Multiplies an input number n times. Default value for n=2
Parameters
----------
input_number : float
Number to be multiplied.
n_value : int, optional
Number of times to multiply input.
Returns
-------
output : float
Result of the multiplication.
"""
# Multiple the input n times
output = input_number * n_value
return output
# Test using `default_function`
print(default_function(2))
print(default_function(2, 10))
# Function values can be also be defined by name
print(default_function(n_value=-1, input_number=2))
print(default_function(input_number=10, n_value=10, ))
Q2 - Default Value Functions (0.5 points)¶
a) Sort Keys¶
Write a function called sort_keys, which will return a sorted version of the keys from an input dictionary.
Input(s):
dictionary: dictionaryreverse: boolean, default: False
Output(s):
sorted_keys: list
Procedure(s):
Get the keys from the input
dictionaryusing thekeys()method (this will return a list of keys)Use the
sortedfunction to sort the list of keys. Pass inreversetosortedto set whether to reverse the sort orderReturn the sorted list of dictionary keys
# YOUR CODE HERE
raise NotImplementedError()
assert callable(sort_keys)
assert isinstance(sort_keys({'B': 2, 'A': 1}), list)
assert sort_keys({'B': 2, 'A': 1}) == ['A', 'B']
assert sort_keys({2: 'B', 1: 'A'}, reverse=True) == [2, 1]
assert callable(sort_keys)
b) List Powers¶
Write a function called list_powers.
This function will take a list, and return a new list where each element is exponentiated to a specified power.
Input(s):
collection- collection of numberspower- int, default value: 2
Output(s):
power_list- list of numbers
Procedure(s):
Initialize an empty list to be filled and returned
Loop through each element in the input
collectionTake the value to the power specified in
powerAppend the result to the output list
Return the list of powers
# YOUR CODE HERE
raise NotImplementedError()
assert callable(list_powers)
assert isinstance(list_powers([1, 2]), list)
assert list_powers((2, 4)) == [4, 16]
assert list_powers([2, 4], 3) == [8, 64]
assert callable(list_powers)
Example: Zip¶
A reasonably common task in Python is to want to iterate across multiple collections items together.
To do so, we have the function zip, which takes in collection types, and let’s you step across them together.
This allows you to get, for example, the 0th item of each collection, and then the 1st item of each collection, etc.
Below is an example of using zip.
# Define some collection objects
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
# Use zip, in a loop, to loop across both collections at the same time
for it1, it2 in zip(list1, list2):
print(it1, it2)
Q3 - Functions for Later (1 point)¶
In this section we will write a set of functions that will be useful for us to use later, with our artificial agents.
a) Add Lists¶
Write a function called add_lists.
Input(s):
list1- list of numberslist2- list of numbers
Output(s):
ouput- list of numbers
Procedure(s):
Initialize a new list
outputUsing
zip, loop through elements of bothlist1andlist2In each iteration, append the sum of the elements to
output
Return
output
# YOUR CODE HERE
raise NotImplementedError()
assert callable(add_lists)
assert [1, 1] == add_lists([0, 0], [1, 1])
assert callable(add_lists)
b) Check Bounds¶
Write a function called check_bounds.
We will be using this function later in our bots. It checks whether a position, given as, for example [2, 3] is within the bounds of a grid of a particular size (say, a 4 x 4 square grid).
Input(s):
position- list of intsize- int
Output(s):
boolean
Procedure(s):
Loop across each element in
position.Check if the element is less than 0, or greater than or equal to
sizeIf so, return
False
Outside and after the loop, return
True
Note that this means the function will return False if any element is outside the specified bounds.
After checking all elements, if it hasn’t returned yet, it returns True to indicate all elements are indeed within the bounds.
# YOUR CODE HERE
raise NotImplementedError()
assert callable(check_bounds)
assert check_bounds([0, 0], 5)
assert not check_bounds([0, -1], 5)
assert not check_bounds([0, 5], 5)
assert callable(check_bounds)
Part 2: Classes¶
In this section we will be working with classes - building several classes to solve a variety of problems.
Q4 - Creating a Class¶
We can start with a class object to make a ‘schedule book’ for our course Office Hours.
Create a class called OfficeHours
Class Attributes:
course, which has the value “COGS18”
Instance Attibutes:
name: stringday: stringtime: stringplace: stringNote: all these attributes should be passed into the
__init__, and attached to the object in there.
Method
check()This method should print out information, to look something like:
Tom's office hours are on Tuesday at 12:30 in CSB115.Note that this should use the instance attributes to print out
name,day,time&placeof the particular instance.
# YOUR CODE HERE
raise NotImplementedError()
assert OfficeHours
assert OfficeHours('name', 'day', 'time', 'room')
name = "Instructor"
day = "Tuesday"
time = "12:30"
place = "CSB115"
test_oh = OfficeHours(name, day, time, place)
assert isinstance(test_oh, OfficeHours)
assert test_oh.course == "COGS18"
assert test_oh.name == name
assert test_oh.day == day
assert test_oh.time == time
assert test_oh.place == place
Here is a list of some office hours:
Tom - Monday @ 12:30 in SDSC 212e
Rob - Wednesday @ 2:00 in CSB 114
Daril - Tuesday @ 10:00 in CSB 115
Paolo - Friday @ 1:00 in CSB 106
Create a new instance of the OfficeHours class for each of the office hours listed above.
Then store each of these instances together in a list called all_office_hours.
# YOUR CODE HERE
raise NotImplementedError()
assert isinstance(all_office_hours, list)
assert len(all_office_hours) == 4
for item in all_office_hours:
assert isinstance(item, OfficeHours)
assert item.name in ['Tom', 'Rob', 'Daril', 'Paolo']
Now write a function called check_all that will take in a list of OfficeHour objects, and will call the check method on each one, using a loop to loop across each element in the input list.
Call this function on your all_office_hours list to make sure that it works as expected.
You should see that running this function prints out a string of information for each object, that looks like:
Tom's office hours are on Monday at 12:30 in SDSC 212e.
Rob's office hours are on Wednesday at 2:00 in CSB 114.
Daril's office hours are on Tuesday at 10:00 in CSB 115.
Paolo's office hours are on Friday at 1:00 in CSB 106.
# YOUR CODE HERE
raise NotImplementedError()
assert callable(check_all)
Q5 - Car Inventory¶
For this question you will create an object to store information on an inventory of cars.
Create a class called CarInventory
Instance Attibutes:
n_cars: intcars: list
Methods
add_car()compare()
Details:
The constructor (
__init__) should take no inputs, but setn_carsto 0, andcarsto be[](an empty list).
Method: add_car¶
Input(s):
manufacturer: stringmodel: stringyear: intmpg: float
Procedure(s):
Create a dictionary from the values passed with the corresponding keys ‘manufacturer’, ‘model’, ‘year’ and ‘mpg’
Append this dictionary to the
carsattributeIncrement the
n_carsattribute by 1
Method: compare¶
This method will compare all the cars on a specified attribute (‘year’, or ‘mpg’), and find the highest and lowest values. It will return either the highest of lowest value, depending on the setting in a parameter direction.
Input(s):
attribute: stringdirection: string, default: ‘highest’
Procedure(s):
Initialize a variable called
lowestto be the 0th element ofcars, and another variablehighestto be the sameLoop through each car in cars
If the value of the attribute for the car is less than the car stored as
lowest, replacelowestto be the current carSimilarly, if the value of the attribute for the car is greater than the car stored as
highest, replacehighestto be the current car
After the loop, if the value of
directionis ‘highest’, set a new variableoutputto be the variablehighestElse if the value of
directionis ‘lowest’, setoutputto belowest
Return
output
# YOUR CODE HERE
raise NotImplementedError()
# Tests for the object
assert CarInventory
test_car_inv = CarInventory()
assert isinstance(test_car_inv, CarInventory)
assert test_car_inv.n_cars == 0
assert isinstance(test_car_inv.cars, list)
# Tests for add_car method
test_inv = CarInventory()
test_inv.add_car('Toyota', 'Prius', 2012, 36)
assert test_inv.n_cars == 1
assert test_inv.cars[0] == {'manufacturer': 'Toyota', 'model': 'Prius', 'year': 2012, 'mpg':36}
# Tests for compare method
test_inv = CarInventory()
test_inv.add_car('Toyota', 'Prius', 2012, 36)
test_inv.add_car('BMW', 'M3', 2017, 27)
assert test_inv.compare('mpg') == {'manufacturer': 'Toyota', 'model': 'Prius', 'year': 2012, 'mpg': 36}
assert test_inv.compare('year', 'lowest') == {'manufacturer': 'Toyota', 'model': 'Prius', 'year': 2012, 'mpg': 36}
b) Using our class¶
Using the inventory instance created for you in the cell below, do the following:
Use
compareto get the car with the highest mpg, and get the manufacturer of that car from the returned carStore the answer in a variable called
highest_mpg
Use
compareto get the car with the lowest year, and get the model of that car from the returned carStore the answer in a variable called
oldest_car
# The following code provided for you
inventory = CarInventory()
inventory.add_car('Ford', 'Mustang', 2004, 20)
inventory.add_car('Honda', 'Civic', 2014, 40)
inventory.add_car('Toyota', 'Corrolla', 2010, 50)
inventory.add_car('Tesla', 'S', 2017, 1000)
# Example: Get the least efficient car (lowest mpg)
inventory.compare('mpg', 'lowest')
# Example: Get the newest car (highest year), and extract it's mpg
inventory.compare('year', 'highest')['mpg']
# YOUR CODE HERE
raise NotImplementedError()
assert isinstance(highest_mpg, str)
assert highest_mpg in ['Ford', 'Honda', 'Toyota', 'Tesla']
assert isinstance(oldest_car, str)
assert oldest_car in ['Mustang', 'Civic', 'Corrola', 'S']
assert highest_mpg
assert oldest_car
Example: Class Inheritance¶
The following is an extra example of class inheritance.
class TritonCourse:
university = "UC San Diego"
def __init__(self, department, code, title):
"""
department: string
code: int
title: string
"""
self.department = department
self.code = code
self.title = title
def print_info(self):
print(self.department + str(self.code) + ': ' + self.title)
# By passing in `TritonCourse` to our new class, we create a derived class that inherits from `TritonCourse`
class TritonProgCourse(TritonCourse):
def __init__(self, department, code, title, language):
# Call the initializer from the class we inherited from
# `super` refers to the class that this class inherits from
super().__init__(department, code, title)
# Add a new instance attribute to our derived class
self.language = language
# Example: create an instance from our derived class
cogs18 = TritonProgCourse('COGS', 18, 'Introduction to Python', 'Python')
# Note that our derived class inherits any class attributes & methods from the inherited class
print(cogs18.university)
cogs18.print_info()
# Our derived class also has any extra and unique attributes and/or methods that we add to it
cogs18.language
# We can check that our course is both an instance of 'TritonCourse', and of 'TritonProgCourse'
print(isinstance(cogs18, TritonCourse))
print(isinstance(cogs18, TritonProgCourse))
Q6 - Extending our Class through Inheritance (0.5 points)¶
Create a TritonCogsCourse class that inherits from TritonCourse.
You can model your answer to this based on how we made TritonProgCourse above.
TritonCogsCourse should inherit from TritonCourse and calls it’s super’s __init__.
In addition, in it’s own __init__, it should take an extra parameter called area, which will store which area of Cognitive Science the class is related to.
area should have a default value of None.
Finally, add a method called has_area that returns True if the instance has an area defined (is not None), and returns False if area is equal to None.
# YOUR CODE HERE
raise NotImplementedError()
assert TritonCogsCourse
test_class = TritonCogsCourse('COGS', '108', 'Data Science in Practice')
assert test_class
cogs108 = TritonCogsCourse('COGS', '108', 'Data Science in Practice', 'Data Science')
assert cogs108
assert cogs108.area == 'Data Science'
assert cogs108.has_area() == True
Part 3: Artificial Agents¶
In this last part of the assignment, we will create some ‘Bots’ - little artificial agents that can move around a predefined space.
They will be in the style of PacMan, or little exploration bots.
Each of these bots will be able to explore a predefined world, which is a grid of ‘dots’ (locations).
This grid has labelled locations that can be referenced by a list of integers, with [0, 0] being the top left most corner, and so on.
Each bot will have a move method, which updates the bot’s position, thus moving it around the world.
As you start building some bots, there will be a series of test cells that test out your bots on a ‘board’.
Make sure these cells run to completion without errors - if the cells with play_board in them raise an error, there is something wrong with your bot.
Note that some errors could come from the functions your wrote above that get used here.
# This function provided to you to run your bots on a grid world
# You don't have to do anything with this function
def play_board(bots, n_iter=25, grid_size=5, sleep_time=0.3):
"""Run a bot across a board.
Parameters
----------
bots : Bot() type or list of Bot() type
One or more bots to be be played on the board
n_iter : int, optional
Number of turns to play on the board. default = 25
grid_size : int, optional
Board size. default = 5
sleep_time : float, optional
Amount of time to pause between turns. default = 0.3.
"""
# If input is a single bot, put it in a list so that procedures work
if not isinstance(bots, list):
bots = [bots]
# Update each bot to know about the grid_size they are on
for bot in bots:
bot.grid_size = grid_size
for it in range(n_iter):
# Create the grid
grid_list = [['.'] * grid_size for ncols in range(grid_size)]
# Add bot(s) to the grid
for bot in bots:
grid_list[bot.position[0]][bot.position[1]] = bot.character
# Clear the previous iteration, print the new grid (as a string), and wait
clear_output(True)
print('\n'.join([' '.join(lst) for lst in grid_list]))
sleep(sleep_time)
# Update bot position(s) for next turn
for bot in bots:
bot.move()
Q7 - Bot Base Class¶
Create a class called Bot. This will be our base class for all the bots that we make in this section.
Instance Attibutes:
character- stringposition- list of [int, int]moves- list of list of [int, int]grid_size-Noneor int
Of these instance attributes, only character should be taken in as an input to __init__, with a default value of 8982.
Inside the init:
self.charactershould be set as thechrof inputcharacterself.positionshould be set to starting position[0, 0]self.movesshould be set as the list of possible moves, which are[[-1, 0], [1, 0], [0, 1], [0, -1]]self.grid_sizeshould be initialized as None
# YOUR CODE HERE
raise NotImplementedError()
assert Bot()
bot = Bot()
assert bot.position == [0, 0]
assert bot.character == chr(8982)
assert bot.moves == [[-1, 0], [1, 0], [0, 1], [0, -1]]
assert bot.grid_size == None
Q8 - WanderBot¶
Create a class called WanderBot, that inherits from Bot. WanderBot is a bot that will wander around randomly.
In the
__init__, it should simply call the init fromsuper(and adds no extra instance attributes)Note that to do this, the
__init__should take acharacterinput, with the same default (8982)This value will need to be passed into the super’s
__init__
Add two methods to WanderBot:
Method: wander¶
This method will choose a random move from the possibilities, making sure that move is valid on the current grid.
Procedure:
Initialize a boolean variable
has_new_posasFalseUse a while loop, with the condition
not has_new_posSet a variable
moveas the ouput of callingrandom.choiceonself.movesAdd
movetoself.position, usingadd_lists(a function we created earlier), and assign the output to a new variablenew_posCall
check_boundsonnew_pos, also passingself.grid_sizeintocheck_boundsAssign the output of
check_boundstohas_new_posThis will lead to exiting the loop when a valid new position has been assigned
Return
new_pos
Method: move¶
No inputs (other than
self) or outputs, just setsself.positionto be the output of callingself.wander().
# YOUR CODE HERE
raise NotImplementedError()
assert WanderBot
wbot = WanderBot()
assert wbot
wbot = WanderBot()
wbot.grid_size = 3
assert wbot.wander()
assert isinstance(wbot.wander(), list)
wbot.move()
assert wbot.position != [0, 0]
# Test out our WanderBot
# You should see a 'bot' that steps around different positions randomly
bot = WanderBot()
play_board(bot, grid_size=5)
# Test out running a group of WanderBots
bots = [WanderBot(character=1175), WanderBot(character=1175), WanderBot(character=1175)]
play_board(bots, grid_size=8, n_iter=25)
Example: Random Probabilistic Choices¶
For our next bot, we want to be able to make random choice, but with a specified probability.
We will use a quick trick to do so:
from the
randommodule, we can use therandom()function to uniformly sample a decimal between 0 and 1by checking if that random sample is below some value
prob, we will (on average) make that choiceprob% of the time
An example of this idea is shown below.
# Print out a random decimal value between 0 - 1
print(random.random())
# Example of a random probabilistic choice with a specified probability
if random.random() < 0.75:
print('This will print out 75% of the time')
Q9 - ExploreBot¶
Create a class called ExploreBot, that inherits from Bot.
ExploreBot is a bot that will explore more systematically, generally continuing in the same direction on consecutive steps.
In the
__init__, it should call the__init__fromsuperNote that to do this, the
__init__should take acharacterinput, with the same default (8982)This value will need to be passed into the super’s
__init__
The
__init__should take an extra input,move_probwith default value of 0.75Add
move_probas an instance attribute, and also add a new instance attributelast_move, that’s initialized asNone
Add three methods to ExploreBot:
Method: biased_choice¶
This method will chose a new move, but be biased to chose the same move as was taken on the previous step.
Procedure:
Initialize a variable called
moveasNoneif
self.last_moveis not equal toNoneUsing the random probability procedure described above, if
random.random()is less thatself.move_probSet
moveto beself.last_move
if
moveis still None, setmoveto be a random choice ofself.movesreturn
move
Method: explore¶
Explore should be the same as the wander() method in WanderBot, with two differences:
Inside the
whileloop,moveshould be the return ofself.biased_choice()(instead ofwander())At the end of the
whileloop (inside the loop), setself.last_movetomove
Method: move¶
No inputs (other than
self) or outputs, just setsself.positionto be the output of callingexplore().
# YOUR CODE HERE
raise NotImplementedError()
assert ExploreBot
ebot = ExploreBot()
assert ebot
assert ebot.last_move == None
assert ebot.move_prob == 0.75
assert ExploreBot(move_prob=0.5)
assert ExploreBot
ebot = ExploreBot()
ebot.grid_size = 3
assert ebot.explore()
assert isinstance(ebot.explore(), list)
ebot.move()
assert ebot.position != [0, 0]
# Test out our ExploreBot
# You should see a 'bot' that steps around different positions, often moving in the same direction
bot = ExploreBot()
play_board(bot)
# Test out running a group of WanderBots & ExploreBots
bots = [WanderBot(character=1175), WanderBot(character=1175),
ExploreBot(character=1127), ExploreBot(character=1127)]
play_board(bots, grid_size=10, n_iter=50)
Q10 - TeleportBot¶
Create a class called TeleportBot, that inherits from WanderBot.
TeleportBot is a modified version of WanderBot, that will generally wander around randomly, but sometimes teleport to a brand new location.
In the
__init__, it should call the__init__fromsuperNote that to do this, the
__init__should take acharacterinput, with the same default (8982)This value will need to be passed into the super’s
__init__
The
__init__should take an extra input,tele_probwith default value of 0.2Add
tele_probas an instance attribute
Add two methods to TeleportBot:
Method: teleport¶
This method will chose a totally random location anywhere on the map, ‘teleporting’ there.
To do so, it needs to return a list of two random numbers that define a location of the map (for the current grid size).
Use random.choice with range and self.grid_size to create a list of two randomly chosen numbers within the grid size.
Note - this will entail using each of these three things twice - one for each number.
Return this list.
Method: move¶
No inputs (other than
self) or outputsUsing the random probability procedure from before, use an
ifwithrandom.random() < self.tele_prob.Inside the
if, setself.positionto the ouput ofself.teleport()
else, setself.positionas the ouput ofself.wander()Note that this bot inherits
wander()from it’s super (WanderBot)
# YOUR CODE HERE
raise NotImplementedError()
assert TeleportBot
tbot = TeleportBot()
assert tbot
assert tbot.tele_prob == 0.2
assert TeleportBot(tele_prob=0.5)
assert TeleportBot
tbot = TeleportBot()
tbot.grid_size = 3
assert tbot.teleport()
assert isinstance(tbot.teleport(), list)
tbot.move()
assert tbot.position != [0, 0]
# Test out our TeleportBot
# You should see a 'bot' that steps around randomly, sometimes teleporting to a new location
bot = TeleportBot()
play_board(bot, grid_size=5)
Running it all together¶
# Define a group of bots
bots = [WanderBot(character=1078), WanderBot(character=1078), WanderBot(character=1078),
ExploreBot(character=1127), ExploreBot(character=1127), ExploreBot(character=1127),
TeleportBot(character=1279), TeleportBot(character=1279)]
# Play the board with our bot list
play_board(bots, grid_size=15, n_iter=50)
Extending Our Agents¶
So far, we have some ‘artificial agents’ that can explore a world, with different strategies to do so.
Note that if you want to explore using these bots some more, you can add a new cell, defining a list of bots (changing some inputs to them, if you want to), and use the play_board function to run a ‘board’ (also changing some inputs to play_board, if you want to).
From here, we could think about ways we could make these agents interact with each other, and/or respond more directly the environment.
More broadly - there is any number of ways we could build in more ‘cognitive’ behaviour, given this starting point.
If you are interested in this topic, extending these kinds of ‘artificial agents’ (or ones like them) is one of your project options.
The End!¶
After you’ve explored running the bots, you are done!
Have a look back over your answers, and also make sure to Restart & Run All from the kernel menu to double check that everything is working properly.
When you are ready to submit your assignment, upload it to TritonED under Assignment-4.