Pre-release material and skeleton programs
Getting to know a skeleton program before the exam, the questions asked about it, and making exam-style changes to one.
Do this lesson in the simulatorAQA's first paper is sat on a computer, and part of it is built around a skeleton program: a complete, working program of a few hundred lines, released to schools before the exam with some preliminary material describing it. In the exam you answer questions about it and change it. OCR and Eduqas do not pre-release a program, but their papers also give you unfamiliar code to read, complete and correct, so the skills in this lesson help on every board.
What a skeleton program is
The skeleton is usually a small game or simulation, in each of the languages AQA supports, including Python. It is deliberately incomplete or improvable: it has features missing, validation not done, and occasionally behaviour that could be better. Everything you learned about classes, data structures, subroutines and exceptions is in use somewhere inside it.
Because you have it in advance, you can know it better than any unseen code. Students who have only run it once are at a real disadvantage against students who have taken it apart.
Studying the skeleton before the exam
Work through it the way a new developer joins a team (lesson A14.6):
- Run it and play with it until you know what it does from the outside.
- Map the structure: draw the class diagram, with attributes, methods, inheritance and aggregation (lessons A1.7 to A1.9), and a structure chart of the main subroutines (lesson A14.4).
- Explain every subroutine in one sentence: what it is given, what it does, what it returns.
- Find the data structures: which lists, dictionaries, 2D arrays, records or files hold the state, and how they change.
- Trace the main loop for one turn, following each call.
- Predict the changes an examiner could ask for, and practise making them: add validation, add a new command or feature, fix a weakness, save and load state to a file, change a rule.
- Practise under time: a change, tested, with evidence, in the time the marks allow.
The questions it brings
Questions on the skeleton come in two kinds:
- Understanding questions: "State the name of a method that overrides a method in its parent class." "Explain why this attribute is private." "What is the purpose of the subroutine
is_free?" "Give an example of a local variable." These test whether you know the program and the vocabulary of A1 and A3. - Modification questions: "Change the program so that a robot cannot move into a shelf." "Add a method to report the battery level." For these you give the changed program code and evidence of testing, typically a screen capture of it running with the data the question specifies.
For modification questions:
- Change as little as possible, in the right place. If there is already an
is_freemethod, use it rather than writing your own check in the main program. - Keep the existing style: names, structure, how messages are printed.
- Test with exactly the data the question gives, and show the output the question asks for. Marks for testing are lost by testing something else.
- Order matters: checks that stop a move must come before the move changes anything.
Reading unfamiliar code quickly
With any code you have not seen, find the entry point (the main program), then the data it sets up, then follow one path through. Give each class and subroutine a one-line purpose in the margin as you go. Do not try to understand every line before answering: most questions need only one part.
# a quick way to see the shape of a class you do not know
class Warehouse:
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.grid = [["." for c in range(cols)] for r in range(rows)]
def add_shelf(self, row, col):
self.grid[row][col] = "#"
def is_free(self, row, col):
return self.grid[row][col] == "."
w = Warehouse(4, 5)
print("attributes:", list(vars(w)))
print("methods:", [name for name in vars(Warehouse) if not name.startswith("__")])
Task: change the skeleton program
The skeleton below simulates a warehouse robot on a grid. Warehouse(rows, cols) makes a grid of "." squares; add_shelf(row, col) puts a shelf "#" on a square; is_free(row, col) returns True if a square on the grid has no shelf; show(robot) prints the grid with the robot as R. A Robot starts at row 0, column 0 with a whole-number battery. Row numbers increase going south, and columns increase going east.
As in the exam, it has weaknesses. Make these changes.
- Change
move(self, direction, warehouse)so that it does these checks, in this order, before anything changes: - ifdirectionis not one of the keys ofRobot.MOVES, raiseValueError("unknown direction"); - ifbatteryis less than 5, printbattery lowand return without moving; - if the new square would be off the grid, orwarehouse.is_freesays it is not free, printblockedand return without moving, using no battery; - otherwise move to the new square and reducebatteryby 5. - Change the main program so that a
ValueErrorfrommoveis caught: print<command>: <message>(for exampleX: unknown direction) instead of the position line, and carry on with the next command.
Do not change Warehouse, the shelves, the starting battery or the list of commands. The robot on the mat stays still.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
class Warehouse:
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.grid = [["." for c in range(cols)] for r in range(rows)]
def add_shelf(self, row, col):
self.grid[row][col] = "#"
def is_free(self, row, col):
return self.grid[row][col] == "."
def show(self, robot):
for r in range(self.rows):
line = ""
for c in range(self.cols):
if r == robot.row and c == robot.col:
line += "R"
else:
line += self.grid[r][c]
print(line)
class Robot:
MOVES = {"N": (-1, 0), "S": (1, 0), "E": (0, 1), "W": (0, -1)}
def __init__(self, battery):
self.row = 0
self.col = 0
self.battery = battery
def move(self, direction, warehouse):
d_row, d_col = Robot.MOVES[direction]
self.row += d_row
self.col += d_col
self.battery -= 5
warehouse = Warehouse(4, 5)
for row, col in [(1, 1), (1, 2), (2, 3)]:
warehouse.add_shelf(row, col)
bot = Robot(20)
for command in ["E", "S", "E", "N", "N", "E", "E", "S", "X", "S"]:
bot.move(command, warehouse)
print(command, bot.row, bot.col, bot.battery)
warehouse.show(bot)
Challenges
- Add a method
charge(self, amount)that adds to the battery but never takes it above 100, and test it with 30 and then 90. - Answer as if in the exam: "State the name of a class attribute in the skeleton program" and "Explain why
is_freeis a method ofWarehouserather than ofRobot." - Add the command
"D"to drop a parcel on the square the robot is on, recording it in the grid as"P". Where must the change be made so that later moves treat it as blocked?