Specifications

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 12

Specification: The Magic Bakery

version 1.25

Typos/errors? Email sarah.clinch@manchester.ac.uk and I'll get them fixed.

Table of Contents

1. Some Basics
2. Terminology
3. UML Structure
4. Specification
5. Rule Clarifications

1. Some Basics

In this coursework we'll be asking you to implement a card game, Kim-Joy's Magic Bakery. This
coursework implements almost all the rules of the basic game and we've provided the original rules
document. This coursework does not require you to implement the Scenarios (i.e., you can ignore pages
8-22 of the rules PDF -- but pay attention to the important note about Playing without Scenarios on
page 23). You are also not required to implement the double-value associated with scoring the
Showstopper Cake (the card is still present in the game, but it counts only as one Customer order if
completed). You are also not required to implement the variant of the take ingredient action in which
players take the unseen top card of the Pantry deck instead of drawing an upturned card in the pantry.

This document sets out the class and method structure that is required in your implementation. We'll
also try and clear up some potential ambiguities in the rules.

This document is pretty long, but you don't need to understand everything straight away. We suggest
reading it through once and then re-reading the relevant parts in more depth as you come to implement
the different parts of the game.

Reminder -- If you're struggling to know where to start, try working through the Getting
Started document.

2. Terminology

Terminology is largely as in the rules, but here are some extras/clarifications:

Collections -- We'll often use this term when referring to one or more classes that extend the
java.util.Collection interface.
Customer / customer order -- These terms are used interchangeably to refer to the cards
represented by the CustomerOrder class. These are the "25 Customer Cards" shown with blue
borders on page 1 of the Rules PDF.
Rules PDF -- The original 24-page game rules document, published by Skybound Tabletop.
Time passes PDF -- A separate one-page document providing a comprehensive set of worked
examples that show how customers are added to and move through the activeCustomers
structure within the Customers class.

3. UML Structure

The structure of the following UML class diagram informs the design of the application. Your
implementation must meet the structure provided in the UML diagram.

✔ A standalone image of the UML diagram is downloadable from Blackboard.

✔ All classes shown in the UML should be declared as public classes.

✔ You can choose to add classes, attributes, and methods not depicted in the UML (although substantial
extras should not be necessary).

✘ In contrast to Coursework 1, the git repository for Coursework 2 contains only the (empty) driver and
one additional Java source file ( StringUtils.java ). The expectation is that you will write the entire
source for all of the remaining classes described in the above UML.

✘ You should not change variable names, class/member modifiers, method parameters or return types.

✘ Note that the driver (and optional GUI application) should be placed outside of the package structure
(we've provided an empty driver in your repository).

✘ We've indicated some of the dependencies with standard Java library classes, but not all. The ones
that have been omitted should be obvious enough (e.g. List refers to the class with the same name
in the java.util package).
4. Specification

Most classes and methods are self-explanatory but the following description should resolve any
ambiguity.

The MagicBakery class

The MagicBakery class is the primary container for the game state and represents a play through
the game. Get methods provide access to key attributes: the pantry and a Collection of players .
Note that the methods getLayers and getBakeableLayers should not return multiple
instantiations of the same layer.

The expected sequence of operations to set up gameplay is as follows:

1. Create a new MagicBakery instance. The three-parameter constructor should be used --- the
first parameter is used to initialise the Random object that is stored in the random attribute and
used when Collections of cards are shuffled. The next two parameters specify paths to files
describing the ingredient and layer cards respectively. (You can find these files in the io
directory of your repository.) These paths can be used in combination with the CardUtils class
to instantiate the pantryDeck and layers . The attributes pantry , pantryDiscard ,
and players should be instantiated with empty values. pantryDeck and
pantryDiscard should have LIFO semantics.
2. Use the startGame method to set up the gameplay based on the number of players, in
accordance with pages 2-3 of the rules PDF. This method takes in a List of playerNames ;
the players attribute should be instantiated with Player objects whose names match those
given in this parameter. A second parameter customerDeckFile specifies the path to a file
describing the customer order cards (you can find this file in the io directory of your repository).
This path should be used to create a new Customers object (see below for more information
about the Customers object. Following the creation of that object, the pantryDeck
should be shuffled once using the Random object stored in this.random and the pantry
should then be populated. One or two CustomerOrders should be drawn (depending on the
number of players). Each player then adds to their hand three cards from the pantryDeck (they
do this in turn, i.e. player A takes all three cards before player B takes any).
3. Each turn, the current player can take a number of actions (this is determined based on the number
of players and can be accessed using the ` getActionsPermitted method). While the
currentPlayer has actions left to take (as indicated by getActionsRemaining ), the
bakeLayer , drawFromPantry , passCard , fulfillOrder and refreshPantry
methods should be used to support the completion of those actions (described in pages 4-5 of the
rules PDF). Note that any cards drawn as a result of garnishing an order during fulfilment should be
added to the player's hand AND also returned by fulfillOrder .

A layer should only be baked if it is "bakeable", i.e., the current player has the required ingredients
(or has enough Helpful Ducks to substitute for any missing ingredients). Likewise, orders can only
be fulfilled or garnished if the cards in a player's hand mean that they are "fulfillable" or
"garnishable" respectively. Get methods for bakeable layers, fulfillable customers and garnishable
customers are included in the MagicBakery UML specification.

When baking a layer, a Layer card will move from this.layers to the current player's hand
-- the corresponding Ingredients will move into the pantryDiscard . When fulfilling an
order, the corresponding ingredients (which may be Ingredients or Layers ) are removed
from the player's hand -- ingredients move into the pantryDiscard , whilst Layers return to
this.layers . When baking a layer or fulfilling an order, a Helpful Duck should only be
removed from the player's hand if necessary to complete the operation (and only if the ingredient
they are substituting for is not an instance of Layer ).

For example, if the bakeLayer method is called and the parameter specifies that it is Pastry
that is to be baked then:

If the current player has a hand containing Butter, Flour and a Helpful Duck, then following the
call to bakeLayer the current player's hand should still contain the Helpful Duck.
If the current player has a hand containing Butter, Butter, Fruit and a Helpful Duck, then
following the call to bakeLayer the current player's hand should contain one Butter plus
the unused Fruit.

When drawing a card from the pantry ( drawFromPantry ), the named card moves into the
current player's hand. The rules state that:

If there’s nothing in the Pantry you want to draw, you may use this action to take the top card
of the Pantry deck.

However, for this implementation, you are not required to implement this option.

The space in the pantry that arises from a player drawing from the pantry is filled with the return
value from a call to drawFromPantryDeck . Note that despite the similarity in their names,
these are different operations that act over different attributes within the MagicBakery instance.

If, during any action, the pantryDeck runs out of cards, then any cards in pantryDiscard
should be removed and added to pantryDeck ; if this results in any cards being added to
pantryDeck , then this deck should then be shuffled once using the Random object stored in
this.random .

4. Once the player has run out of actions, the endTurn method should be used to move between
players (and between rounds if all players have taken a turn in this round). The endTurn method
returns a boolean indicating if there are more turns to follow this one (value is true ) or if the
game has ended (value is false ).

5. At any point, loadState and saveState methods in MagicBakery allow MagicBakery objects
to be serialised out to a file, saving and resuming in-progress games.

6. The printCustomerServiceRecord method prints to the console an indication of how many


players have had their orders fulfilled (and garnished), and how many left the bakery without their
order being completed. The format of this is up to you, mine looks like this:
Happy customers eating baked goods: 4 (1 garnished)
Gone to Greggs instead: 0

The printGameState method prints the information needed for a player to make decisions during
their turn. This includes all layers available for baking, the current contents of the pantry and
player's hand, and the collection of CustomerOrders that represent the customers currently waiting
in the bakery. Again, the format of this is up to you, mine looks like this:

The StringUtils class provides methods designed to make producing the above output easier --
we strongly advise that you use these rather than spend valuable time developing your own custom
layout methods.

printGameState and printCustomerServiceRecord can be called at any time but are most
likely to be useful at the start of a turn (and in the case of printCustomerServiceRecord , at the
end of the game).

The Customers class

The Customers class holds state about the CustomerOrder instances that are:

unseen in the customerDeck ,


waiting for their order to be fulfilled in activeCustomers (no more than three), and
those inactiveCustomers that have left the shop when their order was fulfilled or because too
much time passed.

The Customers class is instantiated with four parameters:

deckFile is the path to a file describing the customer order cards (see Operation #2 of
MagicBakery above);
random is the exact same Random object initialised in the MagicBakery (see Operation #1
of MagicBakery above) and should be retained in this class's random attribute;
layers is a collection of Layer objects that represent the layers that can be used as part of
CustomerOrder recipes;
and numPlayers is the number of players that were added in the call to
MagicBakery.startGame .

As part of the execution of this constructor, the inactiveCustomers attribute should be initialised
as an empty collection -- the ordering semantics of this collection are unimportant. The
activeCustomers attribute should be initialised with the semantics described in the separate Time
passes PDF. The customerDeck attribute is populated by the initialiseCustomerDeck
method (which takes three of the four parameters passed to the constructor). In this method, the
following things happen in order:

1. customerDeck is instantiated as a LIFO data structure,


2. all possible CustomerOrder objects are parsed from the deckFile and shuffled once
3. all Level 1 CustomerOrder cards are filtered from the collection of possible orders and the
appropriate number are added to the customerDeck
4. all Level 2 CustomerOrder cards are filtered from the collection of possible orders and the
appropriate number are added to the customerDeck
5. all Level 3 CustomerOrder cards are filtered from the collection of possible orders and the
appropriate number are added to the customerDeck
6. the customerDeck is shuffled once

There are getter methods for activeCustomers and the customerDeck . Two non-standard get
methods are provided:

1. getFulfillable -- returns the CustomerOrders in activeCustomer that can be


fulfilled using only the Ingredients contained in the parameter hand .
2. getInactiveCustomersWithStatus -- returns the inactiveCustomers whose status
matches the specified CustomerOrderStatus .

The core of the Customers class is activeCustomers , that is the customers currently in the
shop waiting to be served. As such, Customers provides several standard methods seen in other
data structures that tell us about these CustomerOrders . The isEmpty method returns true if
there are currently no customers in activeCustomers , the size method returns the number of
customers in activeCustomers , and the remove method removes the specified customer from
activeCustomers . The peek method returns the customer occupying the "rightmost" of the
three spaces in the shop (if there are three customers in the shop, then this will be the one who least
recently entered activeCustomers ), or null if there is currently noone in the rightmost of
the three spaces. Movement of customers through activeCustomers is handled by the methods
addCustomerOrder , customerWillLeaveSoon and timePasses , which are described in
the separate Time passes PDF.

The drawCustomer method does not operate over activeCustomers , but instead removes a
card from the customerDeck .

The CustomerOrder class

The CustomerOrder class is a representation of a Customer card. It includes its name , details of
the Ingredient s that make up the recipe and garnish , the difficulty level of the
Customer (a value from 1-3) and its present fulfilment status (see the section on The
CustomerOrderStatus Class below). The toString method allows access to the name attribute,
with additional getters provided for recipe , garnish , level and status (a setter is also
provided for status ). Two additional getters, getGarnishDescription and
getRecipeDescription allow recipe and garnish information to be returned in a
String format where each Ingredient (name) is comma-separated.

A CustomerOrder is instantiated using a four-parameter constructor and should initially have a


status of WAITING . This status can be changed through calls to one of the following
methods:

abandon -- sets the status to GIVEN_UP


fulfill -- sets the status to FULFILLED or GARNISHED depending on the given
parameters. The status should be set to FULFILLED if the parameter ingredients includes
all required Ingredient s to fulfil the order (or substitutes them with the appropriate number of
Helpful Ducks), and the requirements for GARNISHED are not met. The status should be set to
GARNISHED if :

the parameter ingredients includes all required Ingredient s to fulfil and garnish the
order (or substitutes them with the appropriate number of Helpful Ducks), and
the garnish parameter is set to true

The fulfill method returns the subset of the ingredients that were used to fulfil (and
optionally garnish) the specified order. E.g. if ingredients contains flour and butter
and sugar , and the order requires only butter and sugar , then the return value of
fulfill will be a List containing the butter and sugar objects from the original
ingredients parameter.

The canFulfill and canGarnish methods provide an indication of whether the parameter
ingredients includes all required Ingredient s to fulfil or garnish the order respectively. Note
that in the latter case, the return value is only whether the order can be garnished -- it may still be
the case that the specified ingredients are not sufficient to fulfil the order. That is, a call to
canGarnish with a particular collection of ingredients may return true without a call to
fulfill with that same collection of ingredients successfully setting the status FULFILLED or
GARNISHED .

Enumerations: CustomerOrderStatus and ActionType

The CustomerOrderStatus enum represents the possible states of a CustomerOrder . Orders


are initialised with a state of WAITING and progress to FULFILLED , GARNISHED or
ABANDONED as specified in the abandon and fulfil method descriptions above. The state
IMPATIENT is used to indicate a CustomerOrder that is expected to leave the shop the next time
that "time passes" (i.e., in accordance with the semantics described in the separate Time passes PDF).

The ActionType enum represents the possible actions that can be taken in a turn:
DRAW_INGREDIENT (from the pantry), PASS_INGREDIENT (to another player), BAKE_LAYER
(using cards in hand), FULFIL_ORDER (using cards in hand) and REFRESH_PANTRY (from the
pantry deck).

The Ingredient and Layer classes

The Ingredient and Layer classes represent the Ingredient and Layer cards of the game
respectively.

An Ingredient has a name , which can be accessed through the toString method.

A Layer has a name and recipe (the collection of Ingredients that are required to bake
the layer). The getRecipe and getRecipeDescription methods behave identically to their
counterparts in the CustomerOrder class. The canBake method provides an indication of
whether the parameter ingredients includes all required Ingredient s (or Helpful Duck
substitutes) to bake the layer.

Ingredient s and Layer s can be sorted and compared. Two objects are considered equal if they
share the same name , and sort behaviour should be to sort the ingredients based on the ASCII values
of their names . That is:

Ingredient("Flour") < Ingredient("Sugar") < Ingredient("flour") < Ingredient("sugar")

Ingredient s and Layers provide a hashCode method that ensures they can be used in, e.g.,
the HashSet data structure. We suggest that you derive the hashcode from an Ingredient 's
name and Layer 's name and recipe . The following things from the Java documentation of
Object.hashcode are worth repeating here:

Whenever it is invoked on the same object more than once during an execution of a Java
application, the hashCode method must consistently return the same integer, provided no
information used in equals comparisons on the object is modified. This integer need not
remain consistent from one execution of an application to another execution of the same
application.
If two objects are equal according to the equals method, then calling the hashCode method
on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals method, then calling
the hashCode method on each of the two objects must produce distinct integer results.
However, the programmer should be aware that producing distinct integer results for unequal
objects may improve the performance of hash tables.

As far as is reasonably practical, the hashCode method defined by class Object returns
distinct integers for distinct objects.

The Ingredient class provides a constant HELPFUL_DUCK which should have a value equal to
the name of the Helpful Duck card.

The Player Class

The Player class represents a player of the game. As indicated in the rules, The Magic Bakery can
be played by 2-5 players.
Each Player is instantiated with a name and empty hand of Ingredient s. The
toString method returns the Player 's name .

The addToHand methods allow either a single Ingredient or List of Ingredient s to be


added to the player's hand . Conversely, the removeFromHand method removes the specified
Ingredient from the player's hand. The getHand method returns the sorted collection of
Ingredient s that make up a Player 's hand, whilst the method getHandStr returns a
comma-separated String representation of that same sorted collection where repeated
Ingredients are abbreviated as per the following example:
"Chocolate, Eggs, Sugar (x2)"

The hasIngredient method indicates whether or not a Player currently holds a specified
Ingredient in their hand.

The Util package

The util package contains three classes: CardUtils and ConsoleUtils are specified in the
UML and (as with the bakery package) you are expected to implement them. StringUtils is
also specified in the UML but we have also provided you with a complete implementation of that class.

CardUtils

The CardUtils class provides methods for reading different types of card ( CustomerOrder ,
Ingredient , Layer ) from file. Note that the methods are all static, and so the expectation is that
you will prevent calls to instantiate the CardUtils class.

The methods readCustomerFile , readLayerFile and readIngredientFile return a


collection of CustomerOrder , Layer and Ingredient objects respectively. This collection
represents the full set of objects read from the specified file, in the order that they appear in the file. The
readCustomerFile method takes an additional parameter layers which includes all the
Layer objects from which CustomerOrder s can be derived -- if an ingredient in a
CustomerOrder is not named in layers , then it must be an Ingredient rather than a
Layer .

The above "read" methods each have a corresponding "stringTo" method:


stringToCustomerOrder , stringToLayers and stringToIngredients . These methods
parse the individual lines of the files that were opened in the "read" methods. Again, the
stringToCustomerOrder takes an additional parameter layers whose purpose is the same as
that in readCustomerFile .

Console Utils

ConsoleUtils provides methods relating to Console input. Unlike the other util classes, it
is intended to be instantiated -- it's constructor initialises the console attribute.

The two readLine methods in ConsoleUtils are a direct mirroring of those provided by its
console object.

The remaining methods are intended as convenience methods for common input operations and should
not return until a valid input has been received. All of these methods include a String prompt among
their parameters. This prompt is the text that is used to tell the players what text they are expected to
input. For example, a call to promptForYesNo with a prompt paramter of
"Do you like mushrooms?" would be expected to print the following:
Do you like mushrooms? [Y]es/[N]o

promptForYesNo and promptForStartLoad return binary values. In promptForYesNo , a


return value of true indicates a yes response, and false indicates a no response. In
promptForStartLoad , a return value of true indicates a start response, and false
indicates a load response.

The promptEnumerateCollection method should be used by other methods


( promptForAction , promptForCustomer , promptForExistingPlayer ,
promptForIngredient ) to prompt using a numbered list of options specified in its collection
parameter. The prompts shown by the promptForAction method should include only those
ActionType s that are presently possible (i.e., the BAKE_LAYER , FULFIL_ORDER and
PASS_INGREDIENT options will only displays if the current player has the cards they need to do
these operations). The promptForCustomer and promptForIngredient methods should ask
the player to enter one of the specified customers and ingredients respectively. The
promptForExistingPlayer method should ask the player to select from the other players in the
game (i.e., the current player should be excluded from the options).

The promptForFilePath method prompts for a file path and returns a new File object
representing that path -- it does not need to check the validity of that path.

The promptForNewPlayers method prompts for 2-5 player names, and returns them as a List
of String objects. It should not be possible to have two players with identical names, even if the
cases used in those names are different.

StringUtils

The StringUtils class provides methods for manipulating Strings and formatting different bakery
object types as Strings. We have provided a complete implementation of all the specified methods
in this class. Note that the methods are all static, and so the constructor in StringUtils never
needs to be called (we have therefore made it private). Key methods in this class are:
customerOrdersToStrings , ingredientsToStrings , and layersToStrings each
return a List of String s; when printed, these Strings will provide a full representation of the
CustomerOrder , Ingredient or Layer objects that were passed as a paramter. That is,
iteratively printing the returned Strings will result in layers, ingredients and orders being printed as in the
screenshot that appears in the MagicBakery portion of this specification document. These methods
all make calls to formatTextAsCardsString to assemble the text into a String representing
multiple cards layed out side-by-side and separated by the CARD_VERTICAL_LEFT and
CARD_VERTICAL_RIGHT separators.

The centreString method is used to pad a given String so that it appears centred in a string of the
specified width. The padString method behaves similarly, but in this case the specified String will
be left-aligned within the return value.
The splitString method splits a String into mutiple substring chunks where the longest chunk is
not more than width characters in length. This is used by the customerOrdersToStrings ,
ingredientsToStrings , and layersToStrings methods to divide their content over multiple
lines.

The replaceLast method replaces the last occurance of the specifed target substring within a
String. If the target isn't present within the specified string, then the original string is returned. The
toTitleCase method converts the given comma-separated string to one in which each comma-
separated element begins with an uppercase letter and the rest is lower case. E.g. BLUE, moon
becomes Blue, Moon .

The above methods amount to a significant amount of code, and we do not suggest that you try
to understand it all. Instead, we expect that your code will need to make calls to:

customerOrdersToStrings
ingredientsToStrings
layersToStrings

Thus, you should understand how to call these three methods and their return values. You should
be able to achieve this with a bit of experimentation, there's no need to understand their implementation.

Error Handling

The readCustomerFile , readLayerFile and readIngredientFile methods of the


CardUtils class should throw appropriate exceptions if there is a problem encountered when
reading from file. The MagicBakery constructor and saveState and startGame methods
should do likewise, as should the constructor of Customers .

The loadState method of MagicBakery will need to throw appropriate exceptions if there are
problems reading the specified file or if that file does not contain a valid MagicBakery instance.

The following should result in an IllegalArgumentException :

Initialising the MagicBakery with an invalid number of players.


Trying to remove from a player's hand an Ingredient that is not present in that player's hand.
Making a call to promptEnumerateCollection with a null or empty collection
parameter.

The WrongIngredientsException is a specialisation of the


java.lang.IllegalArgumentException class. The following should result in a
WrongIngredientsException :

Trying to instantiate a CustomerOrder or Layer with an empty/null recipe (by contrast, it is


possible for a CustomerOrder to have no garnish).
Trying to fulfil a CustomerOrder without all of the required ingredients (or the appropriate
number of Helpful Ducks. Note that Helpful Ducks can replace Ingredients but not
Layers ).
Trying to bake a Layer without all of the required ingredients (or the appropriate number of
Helpful Ducks. Note that Helpful Ducks can replace Ingredients but not Layers ).
Trying to take an Ingredient from the pantry when that Ingredient is not currently in
the pantry .
Trying to give an Ingredient to another player when the current player does not presently hold
that Ingredient .
Trying to remove an ingredient from a Player 's hand when they do not presently hold that
Ingredient .

Trying to garnish an order without all of the required ingredients (or the appropriate number of Helpful
Ducks) could result in a WrongIngredientsException but this is not required. If this exception
isn't thrown, then a sensible fallback behaviour should be used (e.g. fulfilling but not garnishing the
order).

The EmptyPantryException is an unchecked exception that should occur only when the
MagicBakery 's pantryDeck is completely empty, even after merging the pantryDeck and
pantryDiscard . If this happens, players will need to bake with the Ingredient cards that they
hold in their hands.

The TooManyActionsException is a specialisation of the


java.lang.IllegalStateException class. If the current player has run out of actions and tries
to take an action (i.e. they call one of the bakeLayer , drawFromPantry , passCard ,
fulfillOrder or refreshPantry methods in MagicBakery ), then this should result in a
TooManyActionsException .

Code organisation and documentation

In addition to the above functionality, this coursework will require you to:

Organise your code into packages (see Chapter 19 of the course textbook). Package util
contains the helper classes for String and file operations. Package bakery contains game-
specific classes (ingredients, customers, players, errors, and the game engine).

Use Javadoc to document your code (see Chapter 11 of the course textbook). You should Javadoc
all public and protected classes, and all public and protected members. There are some examples
of Javadoc in the provided StringUtils class.

5. Rule clarifications

Taking actions

The number of actions a player may take per turn is described on page 4 of the rules PDF. For the
purposes of this coursework, players must take exactly this number of actions.

You might also like