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:
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?"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.
(Template class omitted; instead we just include the modified version below.)
Comment lines highlight the new methods.
Also, it turns out that there was a bug in the set_state
method of the template caused by a typo (using s
instead of i
). This method was not called in any of the demonstration cells and isn't relevant to the problem. Still, it has been corrected here.
# MCS 275 Spring 2024 Homework 3 Solutions
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 i not in (0,1,2): # previously read `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)
#----------------------------------------------------------------------
#----------------------------------------------------------------------
# START OF METHODS ADDED FOR PROBLEM 2
#----------------------------------------------------------------------
#----------------------------------------------------------------------
def __eq__(self,other):
"Equality of signals means same state"
return self.state == other.state # This is an int==int test
def __bool__(self):
"Boolean coercion indicates whether any movement is allowed"
# Since built-in bool sends integer 0 to False and integers 1,2 to True
# it happens that bool(self.state) is the same as the
# requested behavior!
return bool(self.state)
TrafficSignal
behaves as requested¶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")
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")
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:
position
, a Point2
specifying the starting positiondirection
, a Vector2
specifying the movement directioninitial_delay
, an integer indicating how "gradual" the acceleration will be.update()
is called repeatedly, the robot should behave as follows:initial_delay
time stepsdirection
initial_delay-1
time steps (assuming that is positive)direction
initial_delay-2
time steps (assuming that is position)direction
on each time step thereafterThus, 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.
import sys
d = "/home/ddumas/teaching/mcs275/public/samplecode/oop"
if d not in sys.path:
sys.path.append(d)
from bots import *
class SlowStartBot(Bot):
"""
Robot that initially pauses before each step, but each pause is
shorter than the last until it eventually takes one step per update.
"""
def __init__(self,position,direction,initial_delay):
"""
Initialize bot with given position, direction of movement,
and first pause duration
"""
super().__init__(position)
self.direction = direction
self.initial_delay = initial_delay
self.current_delay = initial_delay
self.waited = 0
self.state = "wait"
def update(self):
if self.state == "wait":
self.waited += 1
if self.waited == self.current_delay:
# We're done with this pause
# set next pause to be shorter
self.current_delay -= 1
# change to "go" mode
self.state = "go"
else:
# state=="go"
# take a step
self.move_by(self.direction)
# if we're still in the "slow start", switch
# to wait mode for the next update.
if self.current_delay:
# reset wait count
self.waited = 0
self.state = "wait"
# This method totally optional!
def __str__(self):
"Human-readable string"
return "{}(pos={},dir={},state={},waited={})".format(
self.__class__.__name__,
self.position,
self.direction,
self.state,
self.waited,
)
# This method totally optional!
def __repr__(self):
"Unambiguous string"
return str(self)
Test instantiation
from plane import Point2, Vector2
S = SlowStartBot(position=Point2(6,4), direction=Vector2(-1,1), initial_delay=4)
S
The __str__
method we included above makes it easy to test the behavior as follows:
for _ in range(20):
print(S)
S.update()