Object Orientated Programming Coursework Solutions.
For our documentation see:
- Report -> Our Solution Description
In this first task we will ask you to implement Java classes, which model the game mechanics of "Scotland Yard" within a given software framework.
Note that you will implement the full version of the game (not the beginners version), but with the following alterations/clarifications:
- Police or Bobbies will not be modelled.
- The Ferry will be modelled.
- Mr X and the detectives will be given variable (user-specified) amounts of tickets at the start, the normal rules for tickets follow:
- When a detective moves, the ticket used will be given to Mr X.
- Used Mr X tickets are discarded.
- The number of rounds in a game is variable (>0) specified by an initial setup rather than fixed to 22 rounds as in the board game.
- In the manual, the round count is defined as the number of transitions between Mr X and the detectives as a whole, this number is different from the number of slots on Mr X's Travel Log because Mr X can use double moves which occupies two slots (e.g. a 22 round game with two double move tickets means Mr X can have up to 24 moves).
- For practical reasons, we've simplified this rule so the game can be set up with a variable max number of moves for Mr X (i.e. the slot count in Mr X's travel log), such that the game is over when Mr X's travel log is completely full, instead of some arbitrary number of rounds.
- Mr X cannot move into a detective location.
- Mr X loses if it is his turn and he cannot make any move himself anymore.
- Detectives lose if it is their turn and none of them can move, if some can move the others are just skipped.
Ticket.SECRET
represents a black ticket, this is used for Mr X's secret moves
Pay special attention to rules for double moves and secret moves, since these are particularly complex.
- Download a copy of the full rulebook from Ravenburger's website
-
The executable is a JAR file, run it on your machine like so:
java -jar <path_to_the_game_jar_file_you_have_just_downloaded_from_above>
This game is the online version with an offline mode in the menu (accessible via Game | Local game
).
For the offline mode, follow the instructions in this video where Sion plays against himself. For the online mode, use the credentials which are available here. Search for your UoB username in the 3rd column and then note down your personal username (integer on in 1st column) and password (alphanumeric string in 2nd column). Do not use personal data for your team's name when connecting to the game server: make up something fun!
-
How do I know I am using an M1. M2 or M3 (Apple Silicon) Mac?
- Click on the
Apple logo
on the menu bar and click onAbout This Mac
. in theChip
section, if you see something that starts withApple
, you are using an Apple Silicon Mac; if it starts withIntel
, you are using an Intel Mac. - For Apple Silicon Mac users, you should look for the following fields when downloading softwares:
Apple Silicon
,arm64
,aarch64
,Arm Mac
,M1 Mac
,M2 Mac
- For Intel Mac users, you should look for the following fields when downloading softwares:
x86_64
,x64
,AMD64
,Intel Mac
- Click on the
-
Connecting to the online game server via SOCKS5 proxy. (OPTIONAL) here
TODO: Familiarise yourself with the rules of the Scotland Yard board game and organise some sessions to play the game!
This is a pair programming exercise, so you are strongly recommended to use version control software such as Git and work in your team using a private online repository. Try to do as much pair programming (one screen, two minds) as possible.
This project uses Maven as a build system. You do not need to understand the inner workings of Maven, but feel free to read up about it here.
TODO: Start by creating a repository with the skeleton code from this zip file in it:
If you use Git, a .gitignore
file is already present with all the correct files to ignore.
TODO: Setup the Project
-
IntelliJ - follow the import guide here . The main test class is
uk.ac.bris.cs.scotlandyard.model.AllTest
, the main class to start the UI isuk.ac.bris.cs.scotlandyard.Main
. -
CLI - type the following command at project root (use PowerShell on Windows):
./mvnw clean compile
The project's main source files are all located in src/main/java
and organised in directories
according to the package name, for example uk.ac.bris.cs.scotlandyard.model.ScotlandYard
is a file
located at src/main/java/uk/ac/bris/cs/scotlandyard/model/ScotlandYard.java
.
The main focus of this project is to write a working Scotland Yard game model, thus your work will
focus around the uk.ac.bris.cs.scotlandyard.model
package. You will only need to edit two
classes: MyGameStateFactory.java
and MyModelFactory
. You are allowed to add new classes to the
package. You are not allowed to modify any of the interfaces or tests.
There are 82+ tests provided for your development. They are located in src/test/java
and organised
in the same directory pattern. You should try to run the tests on the provided skeleton project.
TODO: Test the empty model and observe test failures:
-
IntelliJ (Recommended) - Locate the class
uk.ac.bris.cs.scotlandyard.model.AllTest
in IntelliJ and right click the green play button in the left-hand side gutter (i.e. where the line number is). IntelliJ should run all the tests and present you with a test report. -
CLI - type the following command at project root:
./mvnw clean test
The result will look something like this:
Results : Failed tests: ... Tests in error: .... Tests run: 81, Failures: .., Errors: .., Skipped: 0 ...
Some of the tests are written using AssertJ to simplify the statement of assertions. It will be sufficient for completing this exercise to just read the test names and assertion statements to understand what the tests are testing.
While implementing the model, you may only want to focus on one particular test subset.
-
IntelliJ (Recommended) - each test should have a green play button on the left; clicking on it should run that specific test.
-
CLI - run a single test class by specifying the
test
argument when calling Maven, for example:./mvnw -Dtest=GameStateCreationTest test
You can also run a specific test case in a test class, for example:
./mvnw -Dtest=GameStateCreationTest#testNullMrXShouldThrow* test
For help and guidance with your development and how to get started, take a look at the guide now.
TODO: Pass all tests.
When you're done with the model implementation, you can start the GUI and play your very own Java version of Scotland Yard.
TODO: Start up the GUI and enjoy!
-
Intellij - locate the class
uk.ac.bris.cs.scotlandyard.Main
, press the play button next to the class declaration. -
CLI - type the following command at project root:
./mvnw clean compile exec:java
If everything works and you can complete games then you have a working Scotland Yard model and have completed the CW-MODEL coursework! Before embarking on the next step make sure you have produced a bug-free, stable, well coded and well documented model, which you understand well. Make sure you take some time again to review the object-orientation concepts covered in the course and used in your implementation so you are ready for your presentation and VIVA.
Stage 2 of the coursework (cw-wi) will be released at a later date.
To make the development process smoother, a simple Find node tool is included in the GUI. The tool does not require a working model, however, you need to make sure the project still compiles otherwise the GUI won't start of course.
The Find node tool is located in the menu: Help | Find node
, you should see a window that looks
like this:
Type in the node you want to find in the search bar, you may enter multiple nodes separated by
spaces, e.g 42 42 44 45
. The map supports panning and zooming just like the main game.
Recommended reading:
- Scotland Yard game rulebook
- Guava's Immutable collections page
- Guava's ValueGraph section
- JavaDocs for cw-model, also available as comments in the skeleton
Look around in the uk.ac.bris.cs.scotlandyard.model
package, you can complete this project part by
only using classes from this package and Guava + JDK
standard class library.
To begin, locate the skeleton class uk.ac.bris.cs.scotlandyard.model.MyGameStateFactory
. This is
the main class you need to implement to model the behaviour of our Scotland Yard game. As the name
tells us, this class is a factory that implements the Factory<GameState>
interface. This means
that it has a factory method of some sort (build
in this case) which returns a new instance of GameState
.
As we cae see, this method does indeed exist.
However, we find a placeholder in the position where an implementation needs to be placed:
// TODO
throw new RuntimeException("Implement me");
When you implement a method, you should remove the placeholders as those are only present to
facilitate compilation. Next have a look at the Java documentation for
the uk.ac.bris.cs.scotlandyard.model.Board.GameState
interface. We see that GameState
extends Board
and thus will have to implement 8 methods; 7 inherited from Board
plus
the advance
method it requires. Your factory will need to return an implementation of this
interface. Let this implementation class, which implements GameState
, be called MyGameState
. Since
we only ever need the MyGameState
class to be accessible from the factory, we can implement it as
an inner class of MyGameStateFactory
. In addition, consider that the class can be private
and final
. Adding our 8 methods with placeholder returns of null
and defining required imports
leads to compiling code again:
package uk.ac.bris.cs.scotlandyard.model;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.*;
import javax.annotation.Nonnull;
import uk.ac.bris.cs.scotlandyard.model.Board.GameState;
import uk.ac.bris.cs.scotlandyard.model.Move.*;
import uk.ac.bris.cs.scotlandyard.model.Piece.*;
import uk.ac.bris.cs.scotlandyard.model.ScotlandYard.*;
public final class MyGameStateFactory implements Factory<GameState> {
private final class MyGameState implements GameState {
@Override public GameSetup getSetup() { return null; }
@Override public ImmutableSet<Piece> getPlayers() { return null; }
@Override public GameState advance(Move move) { return null; }
}
}
Next lets start thinking about what state data MyGameState
needs to hold and define some first
attributes. Exploring the getter methods, which we just defined, tells us that we at least need to
hold:
- The
GameSetup
to return it, as well as have access to the game graph and Mr X reveal moves - A
Player
to hold the MrX player and aList<Player>
to hold the detectives - A
List<LogEntry>
to hold the travel log and count the moves Mr has taken - A
Set<Move>
to hold the currently possible/available moves - A
Set<Piece>
to hold the current winner(s)
We also may want to keep track of which Piece
can still move in the current round, and which Piece
s and Player
s are in the game. Note that
many of the collections to be used should be immutable and private as good defensive programming
would prescribe, leading to a number of attributes such as:
...
private final class MyGameState implements GameState {
private GameSetup setup;
private ImmutableSet<Piece> remaining;
private ImmutableList<LogEntry> log;
private Player mrX;
private List<Player> detectives;
private ImmutableSet<Move> moves;
private ImmutableSet<Piece> winner;
...
}
Let us now move on and write a constructor for MyGameState
(consider, once done, which of our
attributes can be be made final
). Our constructor will be called by the build
method of the
outer class MyGameStateFactory
, thus it should make use of at least the information available
there:
- The game setup
- The
Player
mrX - The
ImmutableList<Player>
detectives
In addition, we should provide the remaining players (just MrX at the starting round) and the current log (empty at the starting round) so that the constructor can complete a full initialisation of the game state. Our constructor could, considering the incoming parameters as immutable, start off like this:
...
private MyGameState(
final GameSetup setup,
final ImmutableSet<Piece> remaining,
final ImmutableList<LogEntry> log,
final Player mrX,
final List<Player> detectives){ ... }
...
Note that the constructor is private since only the builder in the enclosing class (and later the
advance method) will be calling it. Now that we have at least the declaration of a MyGameState
constructor available, we should return a new instance of MyGameState
in the build
method
of MyGameStateFactory
:
...
@Nonnull @Override public GameState build(...){
return new MyGameState(setup, ImmutableSet.of(MrX.MRX), ImmutableList.of(), mrX, detectives);
}
...
So far, we took care of an appropriate structure for our implementation, but did not aim at passing any tests yet. We now move on to implementing the constructor in order to check and initialise fields using the parameters passed into the constructor. First, we could initialise the local attributes that are directly supplied by the parameters:
private MyGameState(...){
...
this.setup = setup;
this.remaining = remaining;
this.log = log;
this.mrX = mrX;
this.detectives = detectives;
...
}
Add appropriate checks that these parameters handed over are not null
and you will pass your first
tests in GameStateCreationTest
. Start working your way through the tests guiding your
implementation. You may add checks, entire methods, fields, or even classes as you see fit. For
instance, the test #testEmptyMovesShouldThrow
demands that there is at least one move to play,
otherwise an IllegalArgumentException
should be thrown. Thus, you should add a check in the
constructor like this:
...
if(setup.moves.isEmpty()) throw new IllegalArgumentException("Moves is empty!");
...
Some further checks and tests will be required in the constructor, including checks that all
detectives have different locations, that detectives in the list are indeed detective pieces, MrX is
indeed the black piece, and that there are no duplicate game pieces. Let the tests guide you in this
regard. Having defined our attributes, we can also start returning values in our getter methods (
leave the getWinner
method until later). Note that some getter methods need to return Optional
values:
...
@Override public GameSetup getSetup(){ return setup; }
@Override public ImmutableList<LogEntry> getMrXTravelLog(){ return log; }
@Override public Optional<Integer> getDetectiveLocation(Detective detective){
// For all detectives, if Detective#piece == detective, then return the location in an Optional.of();
// otherwise, return Optional.empty();
}
...
Not all values can be easily assembled - getAvailableMoves()
for instance requires us to find
all moves Player
s can make for a given GameState
. It will be easiest to calculate these
moves upfront in the constructor of a GameState
and store them in moves
, but for such a complex
task it is recommended to use some helper methods to avoid monolithic code and an overlong
constructor. One helper function could be the calculation of single moves, thus consider the below
snippet of code as a start and inspiration on how to implement valid move generation:
...
private static Set<SingleMove> makeSingleMoves(GameSetup setup, List<Player> detectives, Player player, int source){
// TODO create an empty collection of some sort, say, HashSet, to store all the SingleMove we generate
for(int destination : setup.graph.adjacentNodes(source)) {
// TODO find out if destination is occupied by a detective
// if the location is occupied, don't add to the collection of moves to return
for(Transport t : setup.graph.edgeValueOrDefault(source, destination, ImmutableSet.of()) ) {
// TODO find out if the player has the required tickets
// if it does, construct a SingleMove and add it the collection of moves to return
}
// TODO consider the rules of secret moves here
// add moves to the destination via a secret ticket if there are any left with the player
}
// TODO return the collection of moves
}
...
However, the above code is only a starting point for an implementation since DoubleMove
s have to
be implemented too, and they are more tricky to handle. Once moves and all exposed state is computed
and returned by the getter
methods you will pass more tests.
Implementing GameStage#getAvailableMoves
correctly should help pass
through GameStateMrXAvailableMovesTest
and GameStateDetectivesAvailableMovesTest
.
Our attention can now shift towards the GameState#advance
method, whose task it is to return a new
state from the current GameState
and a provided Move
. The GameState#advance
method is central to the behaviour of a game, and most tests depend on this method to verify
behaviours of the players. This is the hardest part to implement, so you may want to break up some
of this logic into separate, smaller private methods. The first thing we must check is that the
provided move
is indeed valid using code similar to:
...
public GameState advance(Move move){
if(!moves.contains(move)) throw new IllegalArgumentException("Illegal move: "+move);
...
}
...
Next, we need to implement different behaviours for applying SingleMove
s and DoubleMove
s, e.g.
we may need destination
or destination2
for enacting moves. To route these different
implementations and find out about the Move
type we can use the visitor pattern. Familiarise
yourself with the interface Move
, which has a generic accept
method to support the Visitor
design pattern:
public interface Move extends Serializable {
...
<T> T accept(Visitor<T> visitor);
...
}
Note that this is very similar to the house visitor we looked at in previous weeks (tasks, solutions). If you have not completed those exercises, it's highly recommended you do so (or at the very least look at the solutions) to understand how the visitor pattern works. If you understand the house visitor, you more or less already understand the move visitor. Just as we used the visitor pattern to distinguish StrawHouses, StickHouses, and BrickHouses, we can use it to distinguish SingleMoves and DoubleMoves.
The generic House.Visitor<T>
interface is very similar to Move.Visitor<T>
, and the
corresponding accept()
methods are effectively identical, as we can see by comparing the implementations in BrickHouse
and SingleMove
(very slightly modified for clarity):
...
public class BrickHouse extends House {
...
public <T> T accept(House.Visitor<T> visitor) { return visitor.visit(this); }
}
final class SingleMove implements Move {
...
public <T> T accept(Move.Visitor<T> visitor) { return visitor.visit(this); }
}
Consequently, we can get access to a particular SingleMove
or DoubleMove
by supplying
a Visitor<...>
object as a parameter to the move.accept(...)
method. This parameter could be an
anonymous inner class instantiation such as:
... = move.accept(new Visitor<...>(){
@Override public visit(SingleMove singleMove){ ... }
@Override public visit(DoubleMove doubleMove){... }
});
Equally, it could be defined in the normal way using a separate file and creating a named class which implements the Move.Visitor<T>
interface, or any of the other ways demonstrated in the house visitor task.
With access to the particular move
to enact we can now implement the update of the state, which
means returning a new GameState
object (since the old one is widely immutable) at the end of
the advance
method. This returned state should be updated with regard to: 1) player locations, 2)
tickets used and handed over to players, 3) travel log if move.commencedBy
is MrX, and 4)
remaining pieces in play for the current round (and if none remain an initialisation of players for
the next round).
This returned state should somehow be updated in the following way (not necessarily in order):
- If it's Mr X's turn (which can be checked using
move.commencedBy
):- Add their move(s) to the log
- If a move should be revealed according to the
GameSetup
, reveal the destination in the log, otherwise keep the desination hidden
- If a move should be revealed according to the
- Take the used ticket(s) away from Mr X
- Move Mr X's position to their new destination
- Swap to the detectives turn
- Add their move(s) to the log
- If it's the detectives' turn:
- Move the detective to their new destination
- Take the used ticket from the detective and give it to Mr X
- Ensure that particular detective won't move again this round (i.e. when
getAvailableMoves()
is called, it won't include any moves from that detective) - If there are no more possible detective moves, swap to Mr X's turn
Make sure to refer back to the Scotland Yard game rulebook, as well as the alterations/clarifications at the top of the page, for more details on the game logic.
A correctly implemented advance
method should pass most tests in GameStatePlayerTest
and GameStateMoveTest
.
Once the selection of moves and the advancement of the GameState
are implemented, the game will
need to determine if someone has won or not. This can again be done in the constructor
of GameState
; if no winner has been determined yet then getWinner()
should return an empty set.
In any case, implement checks for end game conditions and return winners in getWinner()
and your
implementation should then pass most tests in GameStateGameOverTest
.
The detectives win, if:
- A detective finish a move on the same station as Mr X.
- There are no unoccupied stations for Mr X to travel to.
Mr X wins, if:
- Mr X manages to fill the log and the detectives subsequently fail to catch him with their final moves.
- The detectives can no longer move any of their playing pieces.
To finalise your implementation take note of all the methods provided in classes from
package uk.ac.bris.cs.scotlandyard.model
. Read the JavaDocs carefully as most are designed to help
you implement your GameState
in some way. Keep in mind that some tests depend on certain methods
such as advance
to be correct in order to run further assertion down the line. It is highly
recommended that you implement your GameState
in the following sequence:
- The constructor of
GameModel
, including any validation on the parameters. - All getters, excluding
getWinner
. andgetAvailableMoves
. - The
advance
method, together withgetAvailableMoves
. - The
getWinner
method.
Finally, implementing observer-related features in the
file uk.ac.bris.cs.scotlandyard.model.MyModelFactory
will pass most tests in ModelObserverTest
.
This class is a factory again, producing via build(...)
a game Model
which should hold
a GameState
and Observer
list and can be observed by Observer
s with regard to Event
s such
as MOVE_MADE
or GAME_OVER
. Reviewing lecture slides on the observer design pattern should be
sufficient to get going. The chooseMove(...)
method is called when a move has been chosen by the
GUI. It could call the advance(...)
method, check if the game is over, and inform the observers
about the new state and event similar to the code below:
...
@Override public void chooseMove(@Nonnull Move move){
// TODO Advance the model with move, then notify all observers of what what just happened.
// you may want to use getWinner() to determine whether to send out Event.MOVE_MADE or Event.GAME_OVER
}
Now all tests including GameStatePlayoutTest
and ModelObserverTest
should pass, those tests
contain several full game play-outs. If everything works and you can start the GUI (see above) and
complete games then you have a working Scotland Yard model and have completed the CW-MODEL
coursework! Before embarking on the next step make sure you have produced a bug-free, stable, well
coded and well documented model, which you understand well. Make sure you take some time again to
review the object-orientation concepts covered in the course and used in your implementation so you
are ready for your presentation and VIVA. Once you are confident, you can take a look at the final
open-ended task cw-ai - which will be released later.
This second coursework part completes the Scotland Yard project, it is open-ended. Before embarking
on it make sure you have produced a bug-free, stable, well coded and well documented model for
the CW-MODEL
part. Make sure you take some time again to review the object-orientation concepts
covered in the course and used in your implementation so far that you are ready for your
presentation and VIVA.
In this coursework, your programming team of two will create a simple Artificial Intelligence (AI)
component to automate the playing of the game Scotland Yard for MrX, and optionally the detectives.
You will receive the same user interface (UI) from cw-model
that allows you to test and play
against your AI. This coursework part also prepares you for taking part in the purely formative
COMS10017 competition with your own AI component, although you can also take part by playing
manually.
TODO: As in previous courseworks, create a private repository shared within your team with the code from this zip file in it:
If you use Git, a .gitignore
file is already present with all the correct files to ignore.
As in the previous exercise, this project uses Maven as a build system. Please refer to previous coursework descriptions for setup, use and background details if needed.
-
IntelliJ - follow the import guide here. The main class to start the UI is
uk.ac.bris.cs.scotlandyard.ui.ai.Main
. -
CLI - type the following command at project root (use PowerShell on Windows):
# for compilation: ./mvnw clean compile # for starting the UI: ./mvnw clean compile exec:java
The game UI should start. In the game configuration dialog, find the Name me!
AI; selecting this
AI relates to the skeleton AI implementation located in uk.ac.bris.cs.scotlandyard.ui.ai.MyAI
.
Open that file and look around.
Pick a name for your AI implementation. It is recommended that you also rename the class to match your AI name.
When starting the GUI anew you should then find your AI listed in the game configuration dialog. You may have multiple AIs (one for MrX, one for the detectives) implemented later in development. The game model should then scan the classpath, find your AI automatically, and add it to the GUI as an option.
TODO: Name your AI
After naming your AI, you are now ready to start implementing actual AI logic. As you can see in the
skeleton code, your AI class implements uk.ac.bris.cs.scotlandyard.model.Ai
. The game model will
call your Ai's pickMove
method when a move needs to be chosen from the the available moves.
Currently, the skeleton code simply returns a random available move back to the game. Your task is to extend the method so that the returned move is well chosen in order to get MrX closer to winning the game.
The Ai
interface also comes with two further (empty) default methods, which you can implement with
your own custom behaviour.
For example, you may wish to perform setup and clean up tasks in the onStart()
and onTerminate()
methods, respectively.
For help and guidance with your development for a scoring function, a game tree, a Mini-Max based AI for MrX and how to get started, take a look at the guide now.
TODO: Implement a scoring function, a game tree, and a Mini-Max based AI for MrX and enhance it with Alpha-beta Pruning and other features
At this point you should have a well working AI that can look a few rounds ahead. Start up the GUI and test it out!
Before embarking on extensions make sure you have fully finished the cw-model
coursework, all
tests pass and you have produced a bug-free, stable, well coded and well documented model, which you
understand well for both cw-model
and cw-ai
. You should consider the time budget you have left
before working on extensions since there are diminishing returns.
Now, that you have a working Scotland Yard AI for MrX, extend your AI so that there is a fully or partly automated play option for detectives and/or improve your AI further - make it better in terms of game-play or faster. We're going to leave these improvements up to you, so be as creative as possible. Possible extensions could include clever pruning of your game tree, dynamic tree depth management, parallelising your AI, refactoring parts of the project to improve OO properties further etc. Let your imagination run wild!
TODO: Implement extensions, do something impressive.
Recommended reading:
- Scotland Yard game rulebook
- Guava's ValueGraph section
For the game of Scotland Yard, the AI's objective is to make the best possible move towards winning the game. In general, for detectives, a good move is one that will either capture or get close to MrX. For MrX, a good move is usually one that will get away from detectives and open up a wide variety of moves. The focus of the task is on writing an AI for MrX.
A reasonable first step for a MrX AI is the implementation of a scoring method score
for a
given Board
that takes into account attributes such as distance to detectives, freedom of movement
based on available target locations, etc. You should then utilise this method to select an available
MrX move by picking the one that leads to a board with the best possible score for MrX. A most
simple scoring method would consider the neighbouring nodes to MrX's position and check if
detectives are present there. For a more sophisticated scoring method, you should try to incorporate
algorithms such as Dijkstra as discussed in
the lecture to calculate distances from MrX to the detectives amongst other features.
Incorporating your scoring method into the pickMove
method by picking the move that will lead to
the highest scoring board will lead to a look-ahead-one AI, where you compare the fitness of
possible game configurations after your next move. This is a first step towards constructing a game
tree and using the MiniMax algorithm to find a good move for MrX when considering looking into the
future of the game further. Once a Mini-Max implementation is in place, try pruning the tree using
Alpha-Beta-pruning.
Implementing the AI is an incremental process, you are encouraged to launch the UI, select your AI and then observe the behavior of your AI whenever you make major changes. When you run the game with an AI selected, the game map should show move traces of each player which may be useful.
Note on external libraries: you may use libraries that would help you develop a better AI as long as you both clearly cite this in your report and mention it at the beginning of your presentation. In any case, we will assess your work and your understanding of it, not the work others have put in to create libraries.
Note on threading:
The uk.ac.bris.cs.scotlandyard.model.Ai#pickMove
method will be executed on a thread of its own
managed by the runtime. Please explicitly synchronise access to non-thread safe objects yourself. If
you are not sure what threading means, and you are not parallelising your solution, you can
disregard this note.