AQA A level skeleton program 2027 explained: Piles O' Tiles

The June 2027 AQA Paper 1 skeleton program taken apart: what Piles O' Tiles does, the four classes and how they fit, one turn through the main loop, the save-file format decoded, the eleven changes Section D is likely to ask for, and the Section C questions with answers. Five demos you can change and run.

GuideG7.10free, runs in your browser

Open this guide in the app

Every September AQA releases the skeleton program that A level Computer Science students will be examined on the following June, in Paper 1. For June 2027 it is a two-player tile-matching game called Piles O' Tiles. You get the code from your teacher, in Python, C#, VB.NET or Java. AQA owns it, so it is not copied here; open your copy beside this page. What is here is everything you need to take it apart: what the game does, how the four classes fit together, one turn traced through the main loop, the save-file format decoded, the weak spots an examiner is likely to ask you to fix, and the understanding questions that come with a program like this. Every demo below is our own code, written to show one idea from the skeleton on a robot you can change and run.

If you have not met a skeleton program before, read lesson A15.6 first. It explains the two kinds of question the paper asks and how to answer them.

The game in plain words

The board is a grid of piles. In the default game there are 36 piles in a 6 by 6 grid, and every pile starts with three tiles: an A worth 1 point on top, a B worth 2 underneath it, and another A worth 1 at the bottom. A saved game can have any square board and any tiles.

Two players take turns. A turn is:

  1. Choose two different piles by their x and y coordinates.
  2. If both piles have tiles and the top tiles show the same symbol, both top tiles are removed to the discard, and the player scores the two tiles' points added together.
  3. If the symbols differ, the program says what each top tile was and nothing changes.
  4. If either pile is empty, the turn is wasted with the message that an empty pile was chosen.

Play passes to the other player. The game ends when a player's score reaches the maximum score (10 in the default game) or when every pile is empty. The program then names the winner. Look closely: if the scores are equal it prints nothing at all. Remember that; it comes back below.

Before each turn the player sees a menu: display the board, display the discard, display the scores, or take the turn. The board shows each pile as the number of tiles left in it, not the symbol on top, so a player has to remember or guess what is where.

Coordinates and the index formula

The piles live in one flat list, but the player thinks in x and y. x runs from 1 across the columns, y runs from 1 up the rows, and the board is printed with the highest y at the top. One small method turns a coordinate pair into a list index, and every part of the game that touches the board goes through it. Understand it and half of Section C is yours.

For a grid that is size wide, the pile at (x, y) is at index (x - 1) + (y - 1) * size. The minus ones are there because the player counts from 1 and the list counts from 0. The demo drives a robot across the mat to each pile in turn and prints the index the formula gives, so you can see the pattern: one row up adds size to the index.

The robot visits the piles of a 3 by 3 board in the order the list stores them. The chart of the index climbs by one each pile and by three each row, which is exactly what the formula says.
The program
from bugbot import *
connect()

SIZE = 3                     # a small board, so it fits on the mat

def index_of(x, y):
    return (x - 1) + (y - 1) * SIZE

for y in range(1, SIZE + 1):
    for x in range(1, SIZE + 1):
        i = index_of(x, y)
        print("pile (%d, %d) is item %d of the list" % (x, y, i))
        plot("index", i)
        forward(40, distance=8)
        wait(0.05)
    right(60, distance=24)   # back across, one row up
    left(60, distance=24)
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

Try the formula backwards. Given an index, x = i % SIZE + 1 and y = i // SIZE + 1. Nothing in the skeleton does this, which is one reason a question might ask you to add a method that does.

The four classes and how they fit

The program is four classes and a main routine. Draw this as a class diagram before the exam; it is a question that comes up in some form most years.

  • Tile holds a symbol and a points value, with a getter for each. Nothing changes a tile once it is made.
  • Player holds a name and a score, getters for both, and one method that adds a change to the score.
  • Pile is a stack of tiles with a maximum size. Adding puts a tile at the front of its list, removing takes the front tile and returns it, and there are methods to ask whether it is empty, how many tiles it holds, and the symbol of the top tile. It also stores a bonus value that nothing in the skeleton ever reads. Write that down.
  • Game owns everything: the board (a list of piles), the players (a list), the discard (a list of tiles), whose turn it is, the maximum score, a count of turns since the last match, a game-over flag, and the grid size, which it works out as the square root of the number of piles. It holds the main loop and all the display and choice methods.

Three things to notice about the relationships. Game has piles and players and tiles as its parts, so this is composition, shown with a filled diamond on the diagram. There is no inheritance anywhere: no class extends another, and a question asking you to add a subclass is a natural way for an examiner to introduce it. And the naming follows AQA's convention: attributes of Tile, Player and Pile start with a single underscore, meaning protected, and attributes of Game start with two, meaning private. Python enforces the second by mangling the name, and does nothing at all about the first.

A pile is the data structure that matters most. The demo builds one in our own words and shows why the last tile added is the one on top.

Three tiles go on, A then B then A, and the top is the last one added. Removing takes from the top. The chart is the pile's height: up to 3, then down to 1, and the add that would make it 4 is refused.
The program
from bugbot import *
connect()

class Pile:
    def __init__(self, maximum):
        self.tiles = []              # index 0 is the top
        self.maximum = maximum

    def add(self, symbol):
        if len(self.tiles) < self.maximum:
            self.tiles.insert(0, symbol)
        plot("height", len(self.tiles))

    def remove(self):
        top = self.tiles[0]
        self.tiles.pop(0)
        plot("height", len(self.tiles))
        return top

    def top(self):
        return self.tiles[0] if self.tiles else None

p = Pile(3)
for s in ["A", "B", "A"]:
    p.add(s)
    print("added", s, "-> pile is now", p.tiles, "top is", p.top())
    forward(40, distance=5)

print("remove gives", p.remove(), "; top is now", p.top())
print("remove gives", p.remove(), "; top is now", p.top())
p.add("Z")
p.add("Z")
p.add("Q")
print("after three more adds the pile is", p.tiles, "(the fourth tile was refused)")
led("green")
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

Ask yourself what happens if remove is called on an empty pile. In the skeleton it would raise an error, and the only thing preventing that is a check in the main loop that both piles are non-empty before any removing happens. That ordering, check first then change, is the single most examined habit in Section D.

One turn through the main loop

The main loop runs until the game-over flag is set. Each pass does this, in this order:

  1. Show the menu and read choices until the player enters the letter for taking a turn. Any other letter either shows something or is ignored, and there is no way to quit.
  2. Read two piles, and read the second one again until it differs from the first.
  3. Add one to the turns-since-match counter.
  4. If both piles have tiles and the tops match: reset the counter to zero, remove both tops, append them to the discard, add their points to the current player's score, and say so.
  5. Otherwise say what went wrong.
  6. Move to the next player with the remainder operator: (current + 1) % number of players.
  7. Set the game-over flag if any player has reached the maximum score or if no pile has any tiles left.

After the loop, the two scores are compared and the higher scorer is named.

The demo plays a whole game of our own tiny version with the moves scripted in a list, so nothing needs typing. Watch the scores chart and the discard grow. Then change the moves or the tiles and run it again.

Two players, four piles, and the turns scripted in a list. A match scores the two tiles' points, a miss scores nothing, and the turn passes with the remainder operator. Both scores are charted, the robot steps forward on every match, and the game stops the moment Bob reaches the maximum score.
The program
from bugbot import *
connect()

piles = [["A", "B"], ["A", "C"], ["C", "B"], ["B", "B"]]   # index 0 is the top of each pile
points = {"A": 1, "B": 2, "C": 3}
players = ["Ada", "Bob"]
scores = [0, 0]
discard = []
whose = 0
MAX_SCORE = 6

moves = [(0, 1), (2, 3), (0, 2), (1, 2), (0, 3), (2, 3)]   # pairs of pile indexes, one pair per turn

for first, second in moves:
    name = players[whose]
    if piles[first] and piles[second] and piles[first][0] == piles[second][0]:
        t1 = piles[first].pop(0)
        t2 = piles[second].pop(0)
        discard += [t1, t2]
        scores[whose] += points[t1] + points[t2]
        print(name, "matched two", t1, "tiles and scores", points[t1] + points[t2])
        forward(50, distance=8)
    elif not piles[first] or not piles[second]:
        print(name, "chose an empty pile")
    else:
        print(name, "did not match:", piles[first][0], "and", piles[second][0])
    plot("Ada", scores[0])
    plot("Bob", scores[1])
    whose = (whose + 1) % len(players)
    if max(scores) >= MAX_SCORE or all(len(p) == 0 for p in piles):
        print("game over")
        break

print("final scores", scores, "discard", discard)
if scores[0] == scores[1]:
    print("a draw, which the real skeleton never says")
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

Two details of the real loop are worth memorising because they are exactly the kind of thing Section C asks about. The empty-pile check happens after the turns-since-match counter has already been increased, so a wasted turn still counts as a turn without a match. And choosing the same pile twice is caught by a loop that simply asks again, with no message, which is the sort of quiet weakness a question will point at.

The save file, decoded

A game can be loaded from a text file, and the loader is a compact piece of code that examiners like because reading it carefully separates students who understand the data structures from students who do not. The layout, in your own words for your notes, is:

  1. One line: the number of piles. It must be a square number, because the grid size is its square root.
  2. One line per pile: the pile's bonus, then pairs of symbol,points for each tile.
  3. One line: the number of players, then one line per player of name,score.
  4. One line for the discard: pairs of symbol,points, possibly empty.
  5. One line each for the maximum score, whose turn it is, and the turns since the last match.

The trap is in step 2. The loader reads the tile pairs from the end of the line backwards, adding each to the pile as it goes. Because adding puts a tile on top, the pair that appears first in the line ends up on top of the pile. So a line reading 0,A,1,E,1,H,3 gives a pile with A on top, then E, then H at the bottom, and the pile's maximum size is set to the number of pairs, so no more can be added. The demo does the same decoding on a line of our own, and you can rewrite the line to check your understanding.

The pairs are read from the end of the line backwards and each is put on top, so the first pair written is the top tile. The chart shows the pile growing to three.
The program
from bugbot import *
connect()

line = "2,A,1,E,1,H,3"          # bonus, then symbol,points pairs: A on top, H at the bottom
items = line.split(",")
bonus = int(items[0])
pile = []                        # index 0 is the top

for i in range(len(items) - 2, -1, -2):     # last pair first
    symbol, pts = items[i], int(items[i + 1])
    pile.insert(0, (symbol, pts))
    print("added", symbol, "worth", pts, "-> pile is", [s for s, p in pile])
    plot("tiles", len(pile))
    forward(40, distance=6)

print("bonus", bonus, "| top tile", pile[0][0], "| tiles", len(pile), "| maximum", len(items) // 2)
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

The whole load is wrapped in a bare try and except. Any error at all, a missing file, a letter where a number should be, a short line, is caught and reported as "file not loaded", and the program falls back to the default game. A question about that is likely: what does the bare except catch, why is that convenient here and poor practice in general, and how would you report which line was wrong.

Where the skeleton is weak, and the changes you will be asked for

Section D asks you to change the program, and the changes are almost always things the skeleton visibly leaves undone. Here is the list for this one, roughly easiest first, with the approach for each. Practise every one with a copy of your file, timed, and keep your test evidence.

  1. Validate the coordinates. Entering a letter crashes the conversion to a number; entering 0 or 7 on a 6 by 6 board gives an index that points at the wrong pile or off the end of the list. Fix: read the input as text, check it is digits and within 1 to the grid size, and ask again with a message. Check before you use the value.
  2. Say when the same pile is chosen twice. The loop that re-asks is silent. Add the message.
  3. Announce a draw. Equal scores print nothing. Add the third branch.
  4. Use the turns-since-match counter. It is maintained and never read. The obvious rule: if there has been no match for some number of turns, the game ends, or the board is reshuffled, or a player is warned. Expect a rule like that to be specified exactly.
  5. Use the bonus. Every pile stores a bonus that nothing reads. Likely rule: when a pile becomes empty, the player who took its last tile gains its bonus, or a matching pair on a bonus pile scores extra. Add a getter to Pile and use it in the match branch.
  6. Show the symbols, not the counts. A version of the board display that prints the top symbol of each pile, or that marks empty piles, with a new menu option.
  7. Handle a wasted turn properly. Choosing an empty pile currently uses the turn. A question may ask that it does not, which means moving the check before the turn count and before the turn passes.
  8. Save the game. The loader exists and the saver does not. Writing the file format back out, in exactly the layout above, is a classic Section D question worth many marks. Get the tile order right: write the top tile first.
  9. More than two players. The loader accepts any number of players, but the winner check compares only the first two. Fix it to find the highest score among all players, and handle ties.
  10. A quit option, and validation of the menu choice, which currently accepts anything.
  11. Inheritance. A special kind of tile, such as a wildcard that matches any symbol, or a joker worth double, as a subclass of Tile with an overridden method, then made to work in the matching code. This is how an examiner brings inheritance and polymorphism into a program that has none.

The demo takes two of these, coordinate validation and the draw, and shows the shape of the fix. Notice that the validation loop rejects the value and asks again rather than letting a bad value through to be caught later.

Bad coordinates are rejected before anything uses them: a letter, a zero, and a seven all get a message. The chart counts the attempts. Then two equal scores produce the draw message the skeleton lacks.
The program
from bugbot import *
connect()

SIZE = 6
attempts = ["x", "0", "7", "4"]          # what a player might type, in order

def valid_coordinate(text):
    if not text.isdigit():
        print("  not a number:", repr(text))
        return False
    n = int(text)
    if n < 1 or n > SIZE:
        print("  out of range:", n, "(must be 1 to", SIZE, ")")
        return False
    return True

tries = 0
for text in attempts:
    tries += 1
    plot("attempts", tries)
    if valid_coordinate(text):
        print("accepted", text, "after", tries, "attempts")
        forward(40, distance=10)
        break

scores = {"Ada": 7, "Bob": 7}
best = max(scores.values())
winners = [name for name, s in scores.items() if s == best]
if len(winners) == 1:
    print(winners[0], "has won!")
else:
    print("It is a draw between", " and ".join(winners))
led("green")
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

The understanding questions to expect

Section C asks short questions about the code as it stands. For this skeleton the likely ones, with the answers in a sentence:

  • What kind of data structure does Pile implement? A stack: tiles are added and removed at the same end, last in first out.
  • Why does Game calculate the grid size with a square root, and why convert the result to an integer? The board is stored flat, so the width has to be recovered from the count of piles; the square root comes back as a decimal and a range needs a whole number.
  • What is the difference between the attribute names that start with one underscore and two? One is the convention for protected, accessible to subclasses, not enforced by Python. Two is private, and Python mangles the name so it cannot be reached by accident from outside the class.
  • Why are the attributes accessed through getter methods? Encapsulation: the class controls what can be read and changed, so a score can only be altered through the method that adds a change.
  • Is the relationship between Game and Pile composition or aggregation? Composition: the piles exist only as part of the game and are created and destroyed with it.
  • What does the loader return, and what is that data type? Seven values at once, as a tuple, the first being whether the load succeeded.
  • Why does the loop that reads tile pairs count down in steps of two? Each tile is two items in the line, and reading from the end puts the first-written pair on top of the pile.
  • What does the remainder operator achieve in updating whose turn it is? It wraps the player number back to zero after the last player, so turns rotate for any number of players.
  • Why is the empty-pile test needed before the top tiles are compared? Reading the top of an empty pile would index an empty list and raise an error.
  • State a weakness of the bare except in the loader. It hides the real error, so a corrupt file and a missing file get the same message and nothing tells the user which line was wrong.

How to prepare, in four weeks

  1. Week one: run the default game and a loaded game until you know the rules from the outside. Draw the class diagram from memory and check it. Write the one-sentence purpose of every method.
  2. Week two: trace one full turn on paper, then trace the loader against a save file you write yourself. Do the trace-table lesson if tables are slow for you: A15.3.
  3. Week three: make every change in the list above on a fresh copy, one a day, and test each with values that would have broken the original. Keep the screenshots.
  4. Week four: the inheritance change and the save routine, timed. Then answer the understanding questions above without looking, and read A15.5 on presenting code and evidence.

The object-oriented ideas behind all of this are taught in A1.7 Classes and objects, A1.8 Inheritance and polymorphism and A1.9 Aggregation and composition, and the stack in A3.2 Abstract data types and stacks. Each runs on a robot you can program in the page.

This page is our own explanation of a program published by AQA for the June 2027 examination. It is not written or endorsed by AQA, and the skeleton program and preliminary material remain AQA's copyright. Get them from your teacher.

Learn it step by step

These lessons build the same ideas one at a time, each with tasks the simulator marks.

  1. A15.6 Pre-release material and skeleton programs Exam preparation, A level
  2. A1.7 Classes and objects Programming techniques and object-oriented programming, A level
  3. A1.8 Inheritance, polymorphism and overriding Programming techniques and object-oriented programming, A level
  4. A1.9 Aggregation, composition and class diagrams Programming techniques and object-oriented programming, A level
  5. A3.2 Abstract data types and stacks Data structures, A level
  6. A15.3 Trace tables and hand-tracing Exam preparation, A level
  7. A15.5 Writing algorithms and code in the exam Exam preparation, A level
Open the lessons