A document from MCS 275 Spring 2024, instructor Emily Dumas. You can also get the notebook file.

MCS 275 Spring 2024 Homework 3

  • Course Instructor: Emily Dumas

Instructions:

  • Complete the problems below, which ask you to create Python scripts.
  • Upload the files directly to gradescope, i.e. upload the .py files containing your code.

Deadline

This homework assignment must be submitted in Gradescope by Noon central time on Tuesday January 30, 2024.

Collaboration

Collaboration is prohibited, and you may only access resources (books, online, etc.) listed below.

Content

This assignment corresponds to Worksheet 3, which involves operator overloading and inheritance.

Resources you may consult

The materials you may refer to for this homework are:

Point distribution

This homework assignment has 3 problems, numbered 2 and 3. The grading breakdown is:

Points Item
4 Autograder
5 Problem 2
5 Problem 3
14 Total

The part marked "autograder" reflects points assigned to your submission based on some simple automated checks for Python syntax, etc. The result of these checks is shown immediately after you submit.

What to do if you're stuck

Ask your instructor or TA a question by email, in office hours, or on discord.

( No problem 1, as usual )

Problem 2: Overloading the traffic signal

Put the code you write for this problem in hwk3prob2.py.

In the next cell, you'll see code for a class that represents a traffic signal controlling a single direction of traffic. The signal is a panel of three lights (one red, one yellow, one green), and exactly one of them is illuminated at any given time. The state of the light can be referred to by the illumated color, by a state number, or by the intent of the signal, which relate as shown below:

state color intent
0 red stop
1 yellow slow
2 green go

The class given below is functional, in that it supports setting, querying, and changing the state of the light. It can also display itself in various forms.

However, a couple of features are missing. After copying the template code below into hwk2prob2.py, modify it to add the following features:

  1. Boolean coercion: A traffic signal object should be considered True if it allows any flow of traffic (i.e. is green or yellow), and False otherwise. Thus the boolean value answers the question "is any movement allowed?"
  2. Equality testing: If A and B are traffic signal objects, then the equality test A==B should evaluate to True if the two signals are in the same state (i.e. color/intent) and False otherwise.

This means you should add some special methods to TrafficSignal. Do not modify or remove any existing methods or attributes of the template.

In [86]:
# MCS 275 Spring 2024 Homework 3
# Template for problem 2 --- save this in a file called hwk3prob2.py
# and add the features requested in the problem

class TrafficSignal:
    """
    Class representing the state of a 3-color traffic signal (AKA traffic light)
    controlling one direction of traffic.  It can be in three states:
    state 0 = red light = STOP = no traffic is allowed to pass the signal
    state 1 = yellow light = SLOW = traffic at or near signal can proceed, others stop
    state 2 = green light = GO = traffic can proceed normally
    """
    color_map = ("red","yellow","green")
    intent_map = ("stop","slow","go")
    def __init__(self,state=None,color=None,intent=None):
        """
        Initialize new traffic light according to a state number (0,1,2),
        color name (red,yellow,green), or intent (stop,slow,go).  Only one
        of the arguments can be given.  Initially red if no arguments given.
        """
        self.state = 0 # default red
        args_possible = [state,color,intent]
        args_given = [ a for a in args_possible if a is not None ]
        if not args_given:
            # No arguments, so keep default red.  We're done.
            return
        
        if len(args_given) > 1:
            raise ValueError("Can only specify one of `state`, `color`, `intent`.")
        
        # Now we know exactly one argument given.  Which one?
        if state is not None:
            self.state = state
        elif color is not None:
            try:
                self.state = self.color_map.index(color.lower())
            except ValueError:
                raise ValueError("Unrecognized color: "+str(color))
        elif intent is not None:
            try:
                self.state = self.intent_map.index(intent.lower())
            except ValueError:
                raise ValueError("Unrecognized intent: "+str(intent))

    def get_state(self):
        "get the current state number"
        return self.state                
                
    def get_color(self):
        "get the current color"
        return self.color_map[self.state]
                
    def get_intent(self):
        "get the current intent"
        return self.intent_map[self.state]
    
    def set_state(self,i):
        "set the current state number"
        if not isinstance(i,int):
            raise TypeError("State must be an integer")
        if s not in (0,1,2):
            raise ValueError("Invalid state {}".format(repr(i)))
        self.state = i
        
    def set_color(self,c):
        "set the light color"
        if not isinstance(c,str):
            raise TypeError("Color must be a string")
        if c.lower() not in self.color_map:
            raise ValueError("Invalid color {}".format(repr(c)))
        self.state = self.color_map.index(c.lower())

    def set_intent(self,t):
        "set the signal intent"
        if not isinstance(t,str):
            raise TypeError("Intent must be a string")
        if t.lower() not in self.intent_map:
            raise ValueError("Invalid intent {}".format(repr(t)))
        self.state = self.intent_map.index(t.lower())
                             
    def advance(self):
        "advance to the next state in the usual green-yellow-red-green-... cycle"
        self.state = (self.state-1)%3
        
    def show(self):
        "display with unicode characters"
        for i in range(3):
            print("\U0001F534\U0001F7E1\U0001F7E2\u26AB"[i if i==self.state else -1])

    def __str__(self):
        "Human-readable string"
        return "{}[{}={}={}]".format(self.__class__.__name__,self.state,self.get_color(),self.get_intent())

    def __repr__(self):
        "String for use in the REPL (evaluates to a copy of this object)"
        return "{}(state={})".format(self.__class__.__name__,self.state)

    

Example of how the provided TrafficSignal class works

Feel free to skip this section. It's here in case you've been reading the code for the template TrafficSignal class and find it confusing or difficult, and want to see an example of how it would be used.

In [34]:
# Three ways to create a yellow light
T = TrafficSignal(state=1)  # by state number
T = TrafficSignal(intent="slow") # by intent (meaning)
T = TrafficSignal(color="yellow") # by color
In [53]:
# Display the traffic signal as a descriptive string
print(T)
TrafficSignal[0=red=stop]
In [55]:
# Display the traffic signal as an expression that would generate a copy of it
T
Out[55]:
TrafficSignal(state=0)
In [80]:
# When made with no arguments, the traffic signal will start red
T = TrafficSignal()
print(T) # descriptive
T.show() # text graphics
TrafficSignal[0=red=stop]
🔴
âš«
âš«
In [81]:
# The `.advance()` method will move to the "next" color
print("Initial:")
T.show()

for k in range(3):
    T.advance()
    print("Advanced {} times:".format(k))
    T.show()
    print()
Initial:
🔴
âš«
âš«
Advanced 0 times:
âš«
âš«
🟢

Advanced 1 times:
âš«
🟡
âš«

Advanced 2 times:
🔴
âš«
âš«

In [50]:
# We can change the state of the signal arbitrarily.
# Here we do it by specifying intent but set_color
# and set_state work analogously.
T = TrafficSignal(intent="go") # initially green
print(T)
T.set_intent("stop") # now make it red
print(T)
TrafficSignal[2=green=go]
TrafficSignal[0=red=stop]
In [51]:
# You can ask the signal for its current color
# E.g. here are the colors in a full cycle
for _ in range(3):
    T.advance()
    print(T.get_color())
green
yellow
red
In [58]:
# You can ask the signal for its current intent (meaning)
# E.g. here are the intents in a full cycle
for _ in range(3):
    T.advance()
    print(T.get_intent())
go
slow
stop

How TrafficSignal should behave when you're done

The cells below were tested against a solution to the exercise. They check the boolean coercion and equality testing behavior. You can run the same code to check your work.

In [90]:
signalsA = [ TrafficSignal(state=i) for i in range(3) ]
boolsA = [ False, True, True ]

looks_good = True
for i in range(3):
    actual = bool(signalsA[i])
    expected = boolsA[i]
    agree = actual==expected
    looks_good = looks_good and agree
    print("{}: bool({} signal) is {}".format(
        "OK" if agree else "ERROR",
        signalsA[i].get_color(),
        actual,
    ))
print("\nBoolean coercion","is working \U0001F60A" if looks_good else "IS NOT WORKING CORRECTLY \U0001F6A9")
OK: bool(red signal) is False
OK: bool(yellow signal) is True
OK: bool(green signal) is True

Boolean coercion is working 😊
In [91]:
signalsA = [ TrafficSignal(state=i) for i in range(3) ]
signalsB = [ TrafficSignal(state=i) for i in range(3) ]
looks_good = True
for i in range(3):
    for j in range(3):
        actual = signalsA[i]==signalsB[j]
        expected = i==j
        agree = actual==expected
        looks_good = looks_good and agree
        print("{}: {}=={} evaluates to {}".format(
            "OK" if agree else "ERROR",
            signalsA[i].get_color(),
            signalsB[j].get_color(),
            actual,
        ))
print("\nEquality testing","is working \U0001F60A" if looks_good else "IS NOT WORKING CORRECTLY \U0001F6A9")
OK: red==red evaluates to True
OK: red==yellow evaluates to False
OK: red==green evaluates to False
OK: yellow==red evaluates to False
OK: yellow==yellow evaluates to True
OK: yellow==green evaluates to False
OK: green==red evaluates to False
OK: green==yellow evaluates to False
OK: green==green evaluates to True

Equality testing is working 😊

Problem 3: Slow start robot

Put the code you write for this problem in hwk3prob3.py.

This problem continues the work on Bot subclasses that we began in lecture by creating bots.py, and which you also worked on in Worksheet 3.

Create another subclass of Bot called SlowStartBot with the following behavior:

  • The constructor takes three arguments, in this order:
    1. position, a Point2 specifying the starting position
    2. direction, a Vector2 specifying the movement direction
    3. initial_delay, an integer indicating how "gradual" the acceleration will be
  • When .update() is called repeatedly, the robot should behave as follows:
    • At first, it sits still for initial_delay time steps
    • On the next time step, it moves by vector direction
    • Then it waits initial_delay-1 time steps (assuming that is positive)
    • Then it takes one step in direction direction
    • Then it waits initial_delay-2 time steps (assuming that is position)
    • This alternation of waiting and taking one step continues, with each waiting period being one time step shorter than the last one.
    • Once the waiting time has decreased to zero, the robot moves by direction on each time step thereafter

Thus, the class is used for a robot that initially spends a lot of time waiting around instead of moving, but gradually the pauses get shorter until it eventually moves on each time step.

The cell below shows an example of how the class might be instantiated.

In [ ]:
from hwk3prob3 import SlowStartBot
from plane import Point2, Vector2

S = SlowStartBot(position=Point2(6,4), direction=Vector2(-1,1), initial_delay=4)

Revision history

  • 2024-01-25 Initial publication