Guide To Getting Started
Guide To Getting Started
version 1.43
Table of Contents
1. Preliminaries
1.1 Some warnings and notes before you start
2.4 Turn-taking
2.5 Collection/List type cleanup
2.6 ConsoleUtils
2.7 Ingredient and Layer card operations
2.7.3 The Pantry
2.7.3 Layers
2.7.3 Baking
You do not have to follow the instructions in this document if you do not want to, and the
instructions below may not give you a complete solution. They're intended to be a general
guide for those who want some help knowing how to approach the problem.
Your solution must meet the provided specification, so you should take time to carefully read
all instructions and UML class diagrams. Do this before progressing any further with the
instructions in this document!
A Note on List and Collection Types: In these early steps of the Getting Started
guide, the UML diagrams and instructions use the ArrayList type in places where
the full UML has a Collection or List type. This is to allow you to get
functional(ish) implementations of classes prior to release of the course materials on
Data Structures. After the first steps are completed, we instruct you to replace these
with the correct types -- make sure that you do this!
List vs. Collection vs. ...? We're often asked why we use the Collection and
List interfaces as types in our UML diagrams rather than specific implementations
of those interfaces. This is because we want to challenge you to establish the correct
concrete implementation based on the description of the semantics that we give you in
the specification. E.g. What classes are appropriate for a LIFO structure? We also want
you to be aware that you can declare a variable with one type, and then instantiate it
with any compatible type (subclass or implementation). E.g.
List<String> foo = new ArrayList<String>(); . When you write your
implementations, you must declare the variable with the type specified in the
UML. You can then instantiate it with the type you think is most appropriate.
Note that in most of the instructions we suggest some println statements and
other method calls to help you ensure each small step is working as expected. We
don't explicitly tell you when to remove these, but you should remove them when they
are no longer needed.
Run the test.sh file. It might seem counterintuitive to start by running the tests
before any code, but once you know you can run the tests then it's easy to track
your progress. Familiarise yourself with the test output, it should end as follows:
[ 26 containers found ]
[ 0 containers skipped ]
[ 26 containers started ]
[ 0 containers aborted ]
[ 13 containers successful ]
[ 13 containers failed ]
[ 532 tests found ]
[ 0 tests skipped ]
[ 110 tests started ]
[ 0 tests aborted ]
[ 0 tests successful ]
[ 110 tests failed ]
Here, the structural tests and the javadoc tests should run successfully (but fail).
The functional tests won't run at all because they won't compile without functioning
classes in the bakery and util packages. Hence the total number of
failing tests is not the same as the number of tests we tell you that there are
in the Manual.
Do not assume that just because you have zero failing tests, you must be passing
everything -- check the number of successful tests matches the total number
given in the manual.
If you scroll back through the output, you'll see it starts with all the compilation errors for the
functional tests, followed by something like this:
Each of the red lines represented one failing test, and the blue lines are the classes in which
the failing tests can be found. Below this you can view the Stack trace for each failing test:
Note that in this document we only have you write class/method signatures as you
come to write associated the functionality. This means that (due to interdependencies
between classes) functional tests may not compile until very late on. To avoid this, you
could choose to write the class/method signatures now, and then work through adding
functionality in accordance with the rest of this guide.
1. Open the provided driver program ( BakeryDriver.java ). Compile and run the
driver -- nothing will happen because the main method is empty.
2.1.3 Packages
In order to get the tests to pass, you're going to need to get to grips with packaging.
1. Following the materials signposted on this topic, start by creating the bakery
package containing one empty class: Ingredient classes.
2. Re-run the tests, hopefully, at one test now passes.
3. Make your first commit and then push your commit back to GitLab. Use gradescope to
run the tests again, this time remotely. The output should be the same as your local
tests.
At the end of Section 2.2.3, your solution should match the following Stage 1 UML
diagram:
2.2.1 Ingredients and Layers
The above UML for those two classes involves one potentially unfamiliar type,
ArrayList . (As per the above A Note on List and Collection Types this isn't exactly the
same type as is used in the final UML, but it's a good starting point and won't require huge
changes to modify later).
ArrayList is a class defined in the Java API that behaves similarly to an array, but
with dynamic (i.e. not fixed) sizing.
Like an array, you create an ArrayList to hold a given type of object (in this
case Ingredient objects)
Unlike an array, ArrayList is defined outside of the java.lang package
so you will need to import it.
Unlike an array, ArrayList is a class which means that you need to call a
constructor to create it. For example, the following creates an ArrayList to
hold String objects:
Unlike an array, the most common way to add items to an ArrayList does not
involve specifying the index at which they will be added, instead, they're just
added at the next available index.
myList.add("Hello");
myList.add("World");
However, like an array, you can get , set , or remove the item at a given
index (using the linked methods rather than square-bracket indexing). The length
of an ArrayList is retrieved using the size() method.
myList.set(0, "Hi");
myList.remove(1);
System.out.println(myList.size());```
1. Let's fill in the Ingredient class. You can quickly and easily implement everything
in the above UML for this class. Add a couple of lines to the BakeryDriver.java
file to create some Ingredient objects (e.g., "Flour", "Sugar", "Eggs" and "Milk")
and print out the result of calling toString on these objects.
2. Modify BakeryDriver.java to add these new Ingredient objects to an
ArrayList . Run test.sh and see what (if any) output has changed.
3. Next start the Layer class. Start with the recipe attribute, constructor and
getRecipe method. Modify BakeryDriver.java to create a Layer object
passing the ArrayList you created in Step 2 as its recipe parameter.
4. Add the getRecipeDescription method to the Layer class. There are a lot of
ways you can do this, the simplest of which would simply be to use String concatenation
("Hello " + "World") and one of the loops that we used right back in Week 1. Modify
BakeryDriver.java to print the return value from
Layer.getRecipeDescription() . Run test.sh and see what (if any) output
has changed.
Next, look at the CustomerOrder class. Having got to grips with ArrayLists in the Layer class,
there should now not be anything new here, just more of the same.
1. Have a go at implementing all of the attributes and methods outlined in the Stage 1 UML
for this class.
2. Modify BakeryDriver to use the existing Ingredient and/or Layer objects to
make up a CustomerOrder . Run test.sh and see what (if any) output has
changed.
We're going to create a very simple outline for the MagicBakery class.
At this point, your solution should match the Stage 1 UML diagram.
Stage 2 involves making use of some of the File and Console IO we covered in Weeks 5 and
3 respectively. In addition, we'll add the CustomerOrderStatus and Player types.
At the end of Section 2.3.4, your solution should match the following Stage 2 UML
diagram:
2.3.1 Creating Ingredients, Layers and CustomerOrders from the CSV files
In Week 5 we learned different types of File IO, and so we can put this knowledge into effect
next.
To do this, we need to start a new class CardUtils (in the util package). This class
provides helper methods that are designed to parse files and return instances of the various
card types.
1. Assuming we start with the simplest card type, we first need to write the
readIngredientFile and stringToIngredients methods. Each line in the
ingredients.csv file contains two comma-separated entries: a name, and a
count. The name corresponds to the name attribute of the Ingredient class, and
the count is the number of copies of that Ingredient that should be present in the
pantryDeck right at the start of the game (before any cards are placed into the
pantry . Initially, I'd suggest populating stringToIngredients with just a
System.out.println statement that prints the str parameter. Then work on
the implementation of readIngredientFile , ensuring it reads the file specified file
line-by-line and makes a call to stringToIngredients for each line of the file.
2. Modify the constructor of the MagicBakery so that the ingredientDeckFile
attribute is passed to a call to readIngredientFile . Check that the output is as
you expect and that the readIngredientFile method is suitably robust to errors.
3. Revisit the stringToIngredients method, ensuring that each of the passed
Strings is parsed and the appropriate number of Ingredient objects are
instantiated and returned. Add a call to print the size() of the return value of
readIngredientFile , compile and run BakeryDriver to ensure that this size
is as you'd expect (63 for the provided ingredients.csv file).
4. Repeat the above four steps for the Layer objects (the rules tell us that there are
four copies of each type of Layer , so 24 Layers should be created in total).
5. Finally, we'll follow the same steps for CustomerOrders . In this case, we need to
make sure that when stringToCustomerOrder parses the ingredients in a
recipe/garnish, it can tell if these are Layers or not. The parameter layers will
be used to do this, and should contain all of the unique Layer objects created by
readLayerFile (there are 25 CustomerOrder s set out in customers.csv ).
6. Commit and push your code back to GitLab. Check the state of the JUnit tests locally
and on Gradescope.
2.3.2 CustomerOrderStatus
We're missing only one instance variable in the CustomerOrder class. This attribute
status is of type CustomerOrderStatus , which is an enum.
2.3.3 Console IO
In Week 3, we used the Console class to get user input. In the ConsoleUtils class,
we create a wrapper around an instance of Console that allows us to write some helper
methods for common user prompts (e.g. "Yes or No" -- "Do you want to continue? Yes or
no?", "Do you want to overwrite an existing file? Yes or no?").
Now that we have our player names, we can create some Player objects.
1. Outline the basics of the Player class including the constructor, name attribute
and toString method.
2. Add an ArrayList attribute players to the MagicBakery to hold Player
objects. Modify the MagicBakery constructor to instantiate the players variable.
3. Modify MagicBakery.startGame so that instead of printing the return value from
promptForNewPlayers it uses that same return value to create Player objects,
add them to players and print them. (Don't forget to check the number of players,
behaving as per the specification if this is not valid).
4. Add the hand attribute to the Player class and modify the constructor so that this is
initially instantiated as an empty ArrayList . Implement the addToHand ,
removeFromHand , getHand and getHandStr methods and test them using
some Ingredient objects (these could just be manually created objects such as
those we created in Stage 1).
5. Commit and push your code back to GitLab. Check the state of the JUnit tests locally
and on Gradescope.
At this point, your solution should match the Stage 2 UML diagram.
2.4 Turn-taking
Now that we have players in the game, it makes sense to think about how those players can
each take turns as they play.
At the end of Section 2.5, your solution should match the following Stage 3 UML
diagram:
1. Now is a good time to start exploring some new data structures, starting with making an
appropriate choice for the players attribute in MagicBakery . Thinking about the
structures you encountered in Week 7, you need to choose a data structure that:
4. Implement the getPlayers method, again making sure that the method signature
has a return type that matches the type specified in the UML, despite the fact that the
specific instance it returns is not directly of that type (although it should be an
implementation of it).
5. Implement getCurrentPlayer . The specification is fairly open here and does not
specify exactly how you should track whose turn it is. You need to make a design
decision about how you will do this, adding any variables and/or methods needed to
support you in doing this. Not however, that your came should expect endTurn to be
called before changing the current player.
7. Commit and push your code back to GitLab. Check the state of the JUnit tests locally
and on Gradescope.
11. Implement endTurn , ensuring that it can't be called if the player has actions
remaining, that currentPlayer is updated, and that if every player has had a turn
the code prints "New round". Compile and run everything again and check the print
output. Do the player names update after the appropriate number of actions? Does it
correctly identify the end of a round AND then correctly set currentPlayer back to
the Player who took the first turn?
12. Commit and push your code back to GitLab. Check the state of the JUnit tests locally
and on Gradescope.
Having made your first decision about exactly what datatype a given Collection should
be instantiated as, it's probably best to go back and revisit our previous List and
Collection variables that we declared as ArrayList types.
1. Begin with a list of all the ArrayList types defined in the Stage 2 UML. I think that
looks something like this:
CardUtils
ConsoleUtils
Layer
CustomerOrder
MagicBakery
2. Compare each of these Stage 2 UML type definitions with those in the full UML
document. If they're the same, nothing needs to change.
But the UML says that foo should be a Collection , then start by modifying the
type as follows:
b. Check that your new declaration compiles correctly. If there are any errors, they may
be addressed in the next step, but it might be that something else is also needed (e.g.
the addition of an import statement). c. Next consider the instantiation type. Check the
specification for any information about the exact semantics that should be exhibited by
this variable/parameter/return type (usually the latter two are determined by the former,
e.g. the behaviour of the return type of getX is determined by the behaviour of
variable x ). This could be information about which entries are removed from the
collection first, or if the collection can hold duplicate values. d. Use the teaching
materials to select the most appropriate data type for the semantics you have identified.
Ensure that the type you select is compatible with the declaration type specified in the
UML. e. Modify the instantiation of your type to match the type you have identified. For
example, if I determined that foo should actually be of Vector type (seems
unlikely, but ok...) then I'd modify the previous example as follows:
f. Check that your new statement(s) compile correctly, adding any missing import
statements etc. as needed.
4. Commit and push your code back to GitLab. Check the state of the JUnit tests locally
and on Gradescope.
At this point, your solution should match the Stage 3 UML diagram.
2.6 ConsoleUtils
At the end of Section 2.7.4, your solution should match the following Stage 4 UML
diagram:
In the passCard functionality we were working on earlier, the card selection was not
based on any user input. So, let's take a moment now to fix that:
You should now have a game where players can take turns, passing cards between
themselves. Let's make things more interesting by allowing them to draw from the pantry and
bake their first layers.
At the start of the game, cards are dealt from the pantryDeck into the pantry .
You'll have some of this code already, it won't all be new functionality.
7. Commit and push your code back to GitLab. Check the state of the JUnit tests locally
and on Gradescope.
2.7.2 Layers
When playing the card game, all layer cards appear face up in piles. Specifically, all the
biscuit cards are in one pile, the sponges in another etc. This means that the different layers
are all visible at any time so that players can see what they can bake and the recipes they
need to follow. Implement MagicBakery.getLayers .
2.7.3 Baking
When baking a layer, a user must exchange all the ingredients that make up the recipe (or
their duck substitutes) for a Layer card. Cards exchanged by the user will go into the
pantryDiscard pile.
2.7.4 Interactivity
To play the game, users need to be able to see what ingredients are in the pantry, and to
read the recipes of each layer. The StringUtils class provides methods to help with
this.
At this point, you're most of the way done. We're pretty confident you can figure out the rest
on your own, but...
Don't forget to run the tests at regular intervals. If you're not sure why a test is failing, take a
look at the stack trace. If that's not helping, you can also look at the source code for the
relevant test.
You can also get help prior to submission by asking questions in the lab drop-ins or online
forums.