Command parser
Operators type commands into the console in a hurry, with stray spaces, capital letters and the occasional slip of the finger. The flight software must never act on a command it didn't fully understand, and the operator needs to know straight away why a command was refused.
Write the parser that sits between the keyboard and the spacecraft.
Finish stage 1 to unlock this stage.
Write parse_command(text). It ignores case and the spaces around words, returns ("burn", seconds) for a burn with one positive number, and raises ValueError with the reason for anything else. Then try every command and print OK and the result, or REJECTED, the command in quotes and the reason:
OK ('burn', 12.5)
OK ('burn', 3.0)
OK ('burn', 0.5)
REJECTED "burn": burn needs one value: the length in seconds
REJECTED "burn ten": burn length must be a number: ten
REJECTED "burn -4": burn length must be positive: -4
REJECTED "dance": unknown command: dance
REJECTED "": empty command
text.strip().lower().split() gives the words. Converting the length can raise its own ValueError: catch it and raise yours, with a clearer message.
Finish stage 1 to unlock this stage.
Two more commands: set mode X, where X is one of MODES, returns ("set", "mode", X), and abort, with no values, returns ("abort",). Anything else after set is refused with its own reason:
OK ('burn', 12.5)
OK ('set', 'mode', 'safe')
OK ('set', 'mode', 'science')
REJECTED "set mode party": mode must be one of nominal, safe, science
REJECTED "set gain 2": unknown setting: gain
REJECTED "set mode": set needs a setting and a value
OK ('abort',)
REJECTED "abort now": abort takes no values
REJECTED "burn ten": burn length must be a number: ten
REJECTED "dance": unknown command: dance
Add both before the final unknown command line, so it still catches everything else.
Hints
Methods find, count, replace and split
# Searching, counting, and splitting strings
# A snippet of mission log text
report = "Mars rover Curiosity landed. Curiosity is still active."
# .find() returns the index of the first match, or -1 if not found
position = report.find("Curiosity")
print("Curiosity first appears at index:", position)
missing = report.find("Voyager")
print("Voyager index:", missing) # -1 means not found
# .count() returns how many times a substring appears
mentions = report.count("Curiosity")
print("Curiosity mentioned:", mentions, "times")
# .replace() can clean up or swap text - here we redact a name
public_report = report.replace("Curiosity", "[REDACTED]")
print(public_report)
# .split() breaks a string into a list, using a separator
crew_data = "Mark Watney,Botanist,Ares III,Sol 18"
fields = crew_data.split(",")
print("Fields:", fields)
# We can grab individual pieces by index
print("Name:", fields[0])
print("Role:", fields[1])
# Splitting on whitespace - no argument needed
sentence = "Houston we have a problem"
words = sentence.split()
print("Words:", words)
Separate arguments with commas. They're passed on every Run as sys.argv[1:].